Thinkphp 5.0远程代码执行漏洞

前言

该漏洞出现的原因在于ThinkPHP5框架底层对控制器名过滤不严,从而让攻击者可以通过url调用到ThinkPHP框架内部的敏感函数,进而导致getshell漏洞,

漏洞分析

在App.php 的run()方法中的110行
通过self::routerCheck函数进行路由检测

找到routercheck
thinkphp/library/think/App.php :606

我们可以看到又进入$request->path()函数
/thinkphp/library/think/Request.php 行数:416行

又进入pathinfo()函数,

Config::get(‘var_pathinfo’)是配置文件中的设置的参数,默认值为s,从GET中获取键值,然后赋值给routeCheck中的$path
再回到App.php,由于没有在配置文件定义任何路由,所以默认按照方式1解析调度。
接着调用了parseUrl()
thinkphp/library/think/Route.php:1208
从1256进行分析

可以看到解析URL的时候只是将URL按分割符分割,并没有进行安全检测。
其中的路由url从Request::path()中获取
/thinkphp/library/think/Request.php:
继续往后跟
thinkphp/library/think/App.php

Config::get(‘var_pathinfo’)是配置文件中的设置的参数,默认值为s,从GET中获取键值,然后赋值给routeCheck中的$path
我们再回到App.php 行数:606
这里会进行路由检测,检查$check后会进入else分支导入路由配置,接着检测路由url调度结果为$result,如果调度失败且开启了强制路由$must,则报出路由无效,接着进入Route::parseUrl函数,根据$path(自定义url)解析操作
开始跟踪parseUrl函数
/thinkphp/library/think/Route.php

进入parseUrlPath函数

对模块/控制器/操作的url地址分割成数组来返回


回到parseurl(),我们可以看到,返回的结果赋值为$path,提取路由信息又封装到$route,
最后返回,回到App.php
在app.php

找到self::exec()

我们可以看到模块/控制器/操作 的函数为self::module
开始跟踪module函数


我们可以看到,根据$config[‘app_multi_module’]进入多模块部署,$bind为NULL,又进入elseif分支,判断模块是否在禁止的列表里面$config[‘deny_module_list’],而且mmodule存在,$available = true,就不会抛出异常
module函数最后的返回值,发现$controller没有进行过滤,那么此时应该为think\app,也就是return self::invokeMethod($call, $vars);
找到invokeMethod()

此时穿进去的$call也就是$method,是一个数组,第一个元素是一个think\App对象,第二个元素则是调用方法名称的字符串invokefunction,然后通过反射ReflectionMethod获取这个对象下对应的方法
再通过函数$args = self::bindParams($reflect, $vars);获取传入的参数,也就是payload

最后再调用反射$reflect->invokeArgs($args);,将Payload数组传入反射对象函数invokeFunction,完成代码执行。

在攻击时注意使用一个已存在的module,否则会报错 模块不存在


此处在获取控制器名时直接从之前的解析结果中获取,无任何安全检查。

这里对控制器类进行实例化
接着在 thinkphp/library/think/Loader.php:454 找到controller 方法

根据传入的name获取对应的类,如果存在就直接返回这个类的一个实例化对象。
跟进getModuleAndClass方法:Loader.php : 521 行

如果控制器名中有\,就直接返回。
回到thinkphp/library/think/App.php的module方法,

正常情况下应该获取到对应控制器类的实例化对象,而我们现在得到了一个\think\App的实例化对象,进而通过url调用其任意的public方法,同时解析url中的额外参数,当作方法的参数传入。

漏洞影响版本

漏洞修复

在thinkphp5.0版本的thinkphp/library/think/App.php554行,thinkphp5.1版本的thinkphp/library/think/route/dispatch/Url.php63行添加如下代码进行过滤:

1
2
3
if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {
throw new HttpException(404, 'controller not exists:' . $controller);
}

payload:
/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=1.php&vars[1][]=<?php phpinfo();?>

s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

参考

https://paper.seebug.org/770/
http://www.lmxspace.com/2018/12/12/Thinkphp-5-0%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E/
https://blog.51cto.com/superwolf/2338829
https://www.kancloud.cn/zmwtp/tp5/120709