使用PHP作为模板语言

3

编辑: 所有人都提出了很好的观点,专用的模板语言显然是最好的方法。谢谢!

我写了这个快速类来通过PHP进行模板设计 - 我想知道如果我将模板设计开放给用户是否容易被利用(目前没有立即计划,但考虑未来可能出现的情况)。

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;     
}

}

示例用法:

$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);

这里的想法是将模板(PHP代码)存储在数据库中,然后通过使用正则表达式的类运行它,以仅允许允许的方法(if,for等)。有人看到利用漏洞并运行任意PHP的明显方法吗?如果有的话,我可能会采用更标准的模板语言,比如Smarty...


2
你真的不应该这样做。有成千上万种可能会导致问题的方式,而你肯定会错过其中的一些。试图阻止每一个可能性也会使模板变得无用。 - Will Vousden
明白了,我也想到了。这只是为内部使用而已,但出于自己的好奇心,我想了解一下可能存在的漏洞利用方式。 - Kunal
1
只是请注意,Smarty 也不是绝对安全的(http://www.smarty.net/manual/en/language.function.php.php)。 - tadamson
@tadamson:嗯,我以为有一种方法可以禁用那个标签...也许不行。 - Sasha Chedygov
如果您得到了答案,请通过单击其旁边的大绿色复选标记来接受它。谢谢,祝好运。 :) - Sasha Chedygov
5个回答

4
当然。
$template_code = '<?= `rm -rf *`; ?>';

编辑:

一时想不到其他的问题。但您应该知道,如果在同一个Template实例上多次调用render,那么您的范围将受到影响。

例如,如果您执行render('<?php $this->allowed_methods[] = "eval"; ?>'),那么这个Template实例将会接受eval作为下一个渲染函数.. ;)


3
聪明。即使你解决了这个问题,可能还有其他的问题... 最好的办法是不要让他们编写PHP。 - mpen
啊,好的。我会过滤掉反引号运算符... 你还有什么想法吗? - Kunal

4

这不是一个好主意。即使您修复了当前的安全漏洞,肯定会有其他您忽略了的漏洞。如果您真的想让用户拥有此功能,请使用实际的模板语言,比如Smarty或编写自己的模板语言。PHP作为内部使用的模板语言非常好,但不适合作为所有用户都可以使用的开放式模板语言。这样可能会被利用的方法太多了,即使您能捕捉到它们所有,也需要比编写一个真正的模板引擎更多的工作量。


3

您可以使用 { 和 } 来使用变量并运行任何函数。

$template_code = '<?php $f = "phpinfo"; ${"f"}(); ?>';

此外,由于您只是运行代码,它将可以访问函数render可以访问的所有变量。包括调用全局变量来从任何地方修改变量或超级全局变量,例如$_SESSION(一个选项可能是保存登录信息的会话变量,并使用javascript通过ajax发布到另一个站点)。
$a = "hello";
$template_code = '<?php global $a; $a = "test"; ?>';
$params = array('title' => 'My Title', 'photos' => array('img1.jpg', 'img2.jpg'));
$template = new Template;
echo $template->render($template_code, $params);
echo $a;

还有一种滥用允许函数的方法,即创建一个与允许函数同名且其值为任何函数名称的变量。

$template_code = '<?php $if="phpinfo"; $if(); ?>';

1

我以为这样做有用,但是错了吗?

<?php
/* ?> trick your parser by using a comment */
// do whatever unfiltered

如果你真的想做这样的事情,使用tokenizer来解析源代码。但我不建议这样做。事实上,我反对这样做!

+1 你说得对。render('<?php /* ?> */ echo("lol"); ?>') 是可以运行的。请注意,Kunal,有很多种方法可以破坏它 :) - Matt
好的,知道了。使用真正的模板语言。 - Kunal

1
如果您有用户贡献内容,而这些用户不是完全值得信赖的,我建议使用“允许白名单”而不是“拒绝黑名单”标记。
例如,考虑Stack Overflow允许的Markdown格式。它支持非常少的格式选项,其他所有内容都被视为字面文本。大多数用户更喜欢简单的界面,而不是一个说“编写任何代码!但要小心不要破坏应用程序!”的界面。
我的经验法则是:允许用户输入数据和内容;永远不要允许用户输入代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接