如何确保模板引擎在PHP注入


How to secure a template engine in PHP from injection

我目前正试图在PHP中创建一个简单的模板引擎。我主要关心的是安全性,而模板教程不关心。假设我有一个包含用户名和他的描述的数据库表。用户可以在那里输入任何他想要的。

我的猜测是使用htmlspecialchars()函数,以防止javascript和html注入。但是"模板代码注入"呢?如果我的模板规则是将[@key]替换为"value",用户可以更新他的描述,这将干扰我的模板处理程序。当使用set方法时,我应该将"[","@","]"视为特殊字符并将它们替换为它们的ascii码吗?

template.php:

class Template {
    protected $file;
    protected $values = array();
    public function __construct($file) {
        $this->file = $file;
    }
    public function set($key, $value) {
        $this->values[$key] = $value;
    }
    public function output() {
        if (!file_exists($this->file)) {
            return "Error loading template file ($this->file).";
        }
        $output = file_get_contents($this->file);
        foreach ($this->values as $key => $value) {
            $tagToReplace = "[@$key]";
            $output = str_replace($tagToReplace, $value, $output);
        }
        return $output;
    }
}

example.tpl:

用户名:[@ name]
关于我:[@info]

index . php:

include 'template.php';
$page = new Template('example.tpl');
$page->set('info', '[@name][@name][@name]I just injected some code.');
$page->set('name', 'Tom');
echo $page->output();

显示:


用户名:汤姆关于我:TomTomTomI刚刚注入了一些代码。

我使用的代码是基于:

http://www.broculos.net/2008/03/how-to-make-simple-html-template-engine.html

将函数更改为只在未更改的模板中搜索一次已知键:

public function output() {
    if (!file_exists($this->file)) {
        return "Error loading template file ($this->file).";
    }
    $output = file_get_contents($this->file);
    $keys = array_keys($this->values);
    $pattern = '$'[@(' . implode('|', array_map('preg_quote', $keys)) . ')']$';
    $output = preg_replace_callback($pattern, function($match) {
        return $this->values[$match[1]];
    }, $output);
    return $output;
}

我考虑了一下,我认为这个解决方案是最快和最简单的:

foreach ($this->values as $key => $value) {
    $tagToReplace = "[@$key]";
    if (strpos($output, "[@$value]") !== FALSE)
      $value = '['.substr($value,1,-1).']';
    $output = str_replace($tagToReplace, $value, $output);
}

如果输出中有[$value],它将value中的括号替换为html实体字符串。

使用了这个html实体列表

对于未来的采用者:如果模板解析器是通过加载非解释/非求值文件来实现的,那么这种解决方案是可以的(就像OP的情况一样,通过使用file_get_contents加载本地文件)。但是,如果它是通过include PHP视图实现的,请注意,当您将一些用户可修改的数据从数据库直接放入视图(不使用解析器,例如<?=$var;?>),然后对该视图使用模板解析器时,此检查将无法处理这种情况。在这种情况下,解析器无法知道这些数据是否是模板结构的一部分,并且这种检查对它们不起作用。(我不知道该如何妥善处理这个案子。)无论如何,我认为最佳实践是永远不要将敏感数据传递给模板解析器,即使您不在模板中使用它们。当攻击者欺骗解析器来计算他的自定义数据时,他不会得到他没有的信息。最好不要使用模板解析器。