Edit: Great points all around, dedicated templating language is obviously the way to go. Thanks!

I wrote up this quick class to do templating via PHP -- I was wondering if this is easily exploitable if I were ever to open up templating to users (not the immediate plan, but thinking down the road).

class Template {

private $allowed_methods = array(
    'if', 
    'switch', 
    'foreach',
    'for', 
    'while'
);

private function secure_code($template_code) {
    $php_section_pattern = '/\<\?(.*?)\?\>/';
    $php_method_pattern = '/([a-zA-Z0-9_]+)[\s]*\(/';
    preg_match_all($php_section_pattern, $template_code, $matches);
    foreach (array_unique($matches[1]) as $index => $code_chunk) {
        preg_match_all($php_method_pattern, $code_chunk, $sub_matches);
        $code_allowed = true;
        foreach ($sub_matches[1] as $method_name) {
            if (!in_array($method_name, $this->allowed_methods)) {
                $code_allowed = false;
                break;
            }
        }
        if (!$code_allowed) {
            $template_code = str_replace($matches[0][$index], '', $template_code);
        }
    }
    return $template_code;      
}

public function render($template_code, $params) {
    extract($params);
    ob_start();
    eval('?>'.$this->secure_code($template_code).'<?php ');
    $result = ob_get_contents();
    ob_end_clean();
    return $result;     
}

}

Example usage:

$template_code = '<?= $title ?><? foreach ($photos as $photo): ?><img src="<?= $photo ?>"><? endforeach ?>';
$params = array('title' => 'My Title', 'photos' => array('img1.jpg', 'img2.jpg'));
$template = new Template;
echo $template->render($template_code, $params);

The idea here is that I'd store the templates (PHP code) in the database, and then run it through the class which uses regular expressions to only allow permitted methods (if, for, etc.). Anyone see an obvious way to exploit this and run arbitrary PHP? If so, I'll probably go the more standard route of a templating language such as Smarty...

Comments

You really shouldn't do this. There are a thousand ways of causing trouble that you'll inevitably miss. Trying to block each and every one of them would render the templates useless anyway.

Written by Will Vousden

Understood, and figured as much. This is really just for internal use, but was wondering about the exploit possibilities for my own edification.

Written by Kunal

Just to note, Smarty isn't exactly safe either (smarty.net/manual/en/language.function.php.php).

Written by tadamson

@tadamson: Hmm, I thought there was a way to disable that tag... Maybe not.

Written by musicfreak

If you got your answer, please accept it by clicking on the big green checkmark next to it. Thanks, and good luck. :)

Written by musicfreak

Accepted Answer

Sure..

$template_code = '<?= `rm -rf *`; ?>';

Edit:

Can't think of anything else right away. But you should know your scope is compromised IF render is ever called more than once on the same Template instance.

For example, if you render('<?php $this->allowed_methods[] = "eval"; ?>') .. then that instance of Template will have eval as an acceptable function for the next render .. ;)

Written by Matt
This page was build to provide you fast access to the question and the direct accepted answer.
The content is written by members of the stackoverflow.com community.
It is licensed under cc-wiki