内容简介:2019年1月11日爆了一个thinkphp 5.0.*远程rce的漏洞,跟进学习一下。根据ThinkPHP的补丁发现,修复部分在
2019年1月11日爆了一个thinkphp 5.0.*远程rce的漏洞,跟进学习一下。
0x02 漏洞分析
根据ThinkPHP的补丁发现,修复部分在 library/think/Request.php 文件中。
漏洞的修复点是 method 这个函数,我们可以逆这回去看看,哪里调用了这个函数,相关调用方法出现在 library/think/Request.php:541-603 的 isGet 、 isPost 、 isPut 、 isDelete 、 isHead 、 isPatch 、 isOptions 中,也就是说实际上这个函数会在判断请求方式的时候进行调用。
从修复代码来看,漏洞触发点应该是下图中 第8行-第9行 这个部分。
第8行代码有个 var_method 常量,这个常量的定义在 application/config.php 文件中, var_method 对应的值是 _method 。
也就是说,如果我们通过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 中。
protected function __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 中,其值默认为空。
而filter存放的是全局过滤规则。
所以核心关键在于 method 这个函数在 POST 方法下可控,所以这里需要全局搜索一下除了那些http请求类型定义以外,还有哪里调用了这个函数,在 library/think/Request.php:634-661 调用了这个函数。
我们看到如果 $this->mergeParam 为空的情况下,调用 $this->method(true) ,而 true === $method 情况下调用的是 server(‘REQUEST_METHOD’) 。
if (true === $method) { // 获取原始请求类型 return $this->server('REQUEST_METHOD') ?: 'GET';
跟进 server 函数,函数实现在 library/think/Request.php:862 ,这里的 $name 实际上就是 REQUEST_METHOD 。
public function server($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 函数获取过滤器。
跟进一下 getFilter 函数,这里的 $filter=’’ ,而 $default=null 。
protected function getFilter($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 函数进行处理。
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 均可控。
也就是说最后我们需要找到自动触发调用param()函数的地方即可,而在原生 thinkphp 框架下,文件位置在 library/think/App.php:126 ,也就是说原生框架的情况下,如果开启了debug模式,可以直接命令执行。
0x03 动态调试
payload如下所示:
public/index.php?s=captcha _method=__construct&filter[]=system&server[REQUEST_METHOD]=ls -al
在开启 debug 状态之后,在 param 下一个断点 _method=__construct
, filter[]=system
,
server[REQUEST_METHOD]=whoami
。
跟进param函数。
跟进method函数。
跟进server函数,这里input的函数输入分别是
name=REQUEST_METHOD default=null filter="" this->server=REQUEST_METHOD=whoami
跟进input函数中getFilter函数,处理结果返回filter数组,其中filter[0]=system。
而 $data 就是我们刚刚的 $this->server ,对应的值也就是whoami,而 filter[0]=system 。
跟进filterValue函数,最后成功运行了。
0x04 扩展
由于正式系统的情况下,使用debug模式的很少,因此需要找一下不需要debug模式下触发点,而漏洞发现者的思路有点像之前 ThinkPHP 远程代码执行那个思路。
payload:
public/index.php?s=captcha _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al
直接动态调试吧,漏洞调用链是这样的。
先看看 app.php:139 位置,其中var_pathino 默认值为s,也就是说我们通过 s 参数注册了一个 \think\captcha\CaptchaController 的路由,至于为什么会是这样,可以翻一翻我之前的 Thinkphp-5-0远程代码执行漏洞 ,里面针对路由怎么调用进行了详细的说明。
而在 vendor/topthink/think-captcha/src/helper.php 文件中针对captcha这个功能进行了路由注册,所以才能够调用。
而这里的返回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 。
然后继续往下走 thinkphp/library/think/Route.php:873 ,此时使得 $rules[$item]
的值为captcha路由数组,就可以进一步调用到 self::parseRule
函数。
跟进一下此时传递进来的 $route 的值为 \think\captcha\CaptchaController@index ,经过处理之后 routeCheck 函数处理之后 type=method 。
前面我们传入的 type 为 method ,所以进入到 app:exec() 中,会选择 method 这个 case 进行逻辑处理,而这个 case 正好调用了 param 这个函数,那么后面的流程自然就和 0x02部分 一样了。
0x05 总结
目前来看,漏洞触发需要两个前置条件,一种情况下如果采用thinkphp原生框架,需要在debug模式下才能够触发。另一种情况是找到一些第三方组件,并且该组件注册了thinkphp的路由,因为这步操作的影响就是改变了上文提到的 self::$rules
的值,而thinkphp自带的一些第三方组件下,好像也只有captcha这个组件,学习了。
和同事在讨论过程中,发现下面这个poc用来验证比较准确点。
public/index.php?s=captcha _method=__construct&method=get&filter[]=var_dump&server[REQUEST_METHOD]=this_is_a_test
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- iphone – 异步函数执行?
- golang函数调用的执行逻辑
- python性能优化之函数执行时间分析
- [ PHP 内核与扩展开发系列] 函数返回值:引用参数与函数的执行结果
- [ PHP 内核与扩展开发系列] 函数返回值:引用参数与函数的执行结果
- PHP中exec()函数执行系统命令失败
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML5与CSS3基础教程(第7版)
[美] Elizabeth Castro、[美] Bruce Hyslop / 望以文 / 人民邮电出版社 / 2013-1 / 59.00元
代表下一代网页编写技术的HTML5,为网页提供布局和格式的CSS3,这两者构成了Web开发的基石,也是Web程序员和设计师必须熟练掌握的最基本技能。 本书是风靡全球的HTML和CSS最佳入门教程的最新版,上一版单单英文版的销量就超过100万册,被翻译为十多种语言,并长期雄踞亚马逊书店计算机图书排行榜榜首。 最新的第7版秉承前一版直观、透彻、全面、循序渐进的讲授特色,仍然采用独特的双栏图......一起来看看 《HTML5与CSS3基础教程(第7版)》 这本书的介绍吧!
HTML 编码/解码
HTML 编码/解码
XML、JSON 在线转换
在线XML、JSON转换工具