我目前正试图在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;?>
),然后对该视图使用模板解析器时,此检查将无法处理这种情况。在这种情况下,解析器无法知道这些数据是否是模板结构的一部分,并且这种检查对它们不起作用。(我不知道该如何妥善处理这个案子。)无论如何,我认为最佳实践是永远不要将敏感数据传递给模板解析器,即使您不在模板中使用它们。当攻击者欺骗解析器来计算他的自定义数据时,他不会得到他没有的信息。最好不要使用模板解析器。