漏洞简介
漏洞存在版本:5.0.0<=ThinkPHP5<=5.0.23,5.1.0<=ThinkPHP<=5.1.30,在开启debug模式的情况下(默认不开启),传入payload可以修改Request类的成员变量,控制filter
,和server[REQUEST_METHOD]
最后在filterValue
中的call_user_func
造成任意代码执行
漏洞复现环境
- php7.4.11
- apache2
- ThinkPHP5.0.23
1 | server%5BREQUEST_METHOD%5D=dir&_method=__construct&filter%5B%5D=system |
部分payload
1 | # ThinkPHP <= 5.0.13 |
漏洞分析
数据包
1 | POST / HTTP/1.1 |
发送数据包后进入start.php
的APP::run()->send()
方法
然后跟进到thinkphp/library/think/App.php
中进行模块/控制器绑定,默认语言加载、系统语言包,在未设置调度信息进行URL路由检测,调用的是self::routeCheck
在Route::check
中进行路由获取
在check方法中调用了$request->method()
获取$_SERVER[REQUEST]
,method方法是关键所在
由于$method
默认是false,因此进入第二个分支,先从config文件中获取var_method的值,翻看config.php文件
得到var_method对应的是_method
,而$this->method
获取的是$_POST[_method]
的大写,因此是我们传入的__construct
的大写,即__CONSTRUCT
,由于没有对$method
进行过滤,因此调用了Request类的构造方法,传入变量是$_POST
变量
在构造方法中,检查Request类中是否含传入的post数组中的变量,存在则存入属性中,即$this->server[REQUEST_METHOD]
会赋值为dir
即我们要执行的命令,$this->filter
会赋值为system
,至此赋值结束
回到thinkphp/library/think/App.php
的routeCheck
逻辑,路由无效时会再调用parseUrl对url进行解析
返回thinkphp/library/think/App.php
的run方法逻辑,如果开启了debug模式的话self::debug的值为true,会调用$request->param()
方法进行变量的读取
在param
方法中如果$this->mergeParam
是空值(默认是false,即默认会触发)则会调用method方法,传入变量为true,true意味着会进入$this->server方法
server方法中,$this->server
是之前赋值的REQUEST_METHOD=dir
,$name
是REQUEST_METHOD
在input方法中对$name
进行一系列处理后取出$this->server``中的REQUEST_METHOD
即我们传入的dir
然后下面就是getFilter
方法,在原来filter的基础上加一个null,接着就进入到关键方法filterValue
先用array_pop弹出一个值,剩下的filters进行foreach获取值,判断是否可调用,这里我们的$this->filter
是传入的system
,$value
是dir
,进入call_user_func
就是system('dir')
至此,漏洞分析完成,总的来说,关键出现问题的地方就是在Request.php的method
方法中,使用了用户传入的值进行动态函数调用,修改了Request类的对象的值,因此才有后面的利用
漏洞修复
判断$method
是否是数组(‘GET’, ‘POST’, ‘DELETE’, ‘PUT’, ‘PATCH’)中的元素,才会进行调用