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…
Advertisement
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 .. 😉