旧洞重提,我能学到什么

admin 2021年5月10日01:32:07评论57 views字数 3595阅读11分59秒阅读模式

本文作者:Z1NG(信安之路荣誉作者)

近来在阅读 ThinkPHP5 源码,在阅读到 Request 类文件的时候想起 ThinkPHP5 之前报过两次 RCE 漏洞,于是试着分析了一下这两枚漏洞。在之前还没有能力分析框架的漏洞,调试漏洞也只是拿着 POC 一路跟进,缺乏思考。如今旧洞重提,我能学到还有很多。之前一个大佬我说,分析漏洞并不只是梳理成因和利用过程,还要思考别人是如何能发现问题,自己有什么不足。于是,就有了下文。

变量覆盖引发 RCE

在 thinkphplibrarythinkRequest.php 的第 1090 行,有个可能够执行RCE的操作。前提是要能控制 $value 和 $fileter 变量。

旧洞重提,我能学到什么

通过回溯,可以找到 thinkphplibrarythinkRequest.php 的 input 方法在第 1036 和 1038 行调用了该方法。

旧洞重提,我能学到什么

根据 input 的参数列表,需要继续回溯

旧洞重提,我能学到什么

在 thinkphplibrarythinkRequest.php 的 param 方法中,看到了 $this->param 的来源是 $_GET和 $_POST 等传参方式的合并。因此可以控制 $this->param,相应的控制了 filterValue 中的 $data , call_user_func 中的 $value

旧洞重提,我能学到什么

但是最大的一个问题是,如何控制 $filter ? 也就是 call_user_function 的 $filter。在 input 方法中,有如下的调用,跟进 getFilter 看看。

旧洞重提,我能学到什么

简而言之,当传入的 $filter 值为空,那么 $filter 的值来源于 $this->filter。

旧洞重提,我能学到什么

到这里可以想到的一个思路,可使用反序列化的方式,注入一个 Request 的对象,并且调用 Param 方法,这样就可以一路到达 RCE 的现场。

如果细细看 Request 的每个方法,不难发现如下代码

旧洞重提,我能学到什么

在 525 行处,由于我们可以控制 $this->method 的值,以致于可以执行 Request 类中的任意方法,并且传入的参数也是我们可控的。可以通过执行 Request 的 __construct,显然 137 行处是一个变量覆盖,可以将 Request 的各个属性的值进行控制。这样我们就可以控制到了 $this->filter

旧洞重提,我能学到什么

到这里为止,已经可以控制了 call_user_func 的两个参数,执行任意命令。那么现在要解决的问题就是如何触发?换句话说,要满足什么条件才可以执行到 Request 的 method 方法。

回溯发现 Route.php 中的 check 方法调用了 method 方法,值得注意的是,调用 method 的地方很多,选用此处的原因是因为没有给 $method 传值,这样默认 $method 默认就是 false 了,能够顺利触发 __construct 中的变量覆盖。

旧洞重提,我能学到什么

在 App.php 中的 routeCheck 调用该方法。

旧洞重提,我能学到什么

看上述代码,需要满足的条件是开启了路由,而默认是开启了路由,这样顺利就能执行了 Route::check 后续的也能顺利执行。

旧洞重提,我能学到什么

回顾上面的流程,理一下思路,想办法执行 method 的目的是为了控制 call_user_func 的参数。而触发代码执行并不是 method 方法,而是要有一处执行了 param 方法的地方。 

全局搜索,会发现 App::exec 执行了这个方法,前提是 $dispatch['type'] 为 method,意味着是要路由到类的方法,才可以执行到分支。

旧洞重提,我能学到什么

显然和 routeCheck 还有关系,继续回头看,要如何满足这个条件。

旧洞重提,我能学到什么

thinkPHP5 中自带了一个验证码组件已经注册到了如图之中,并且就是路由到类的方法,显然是满足条件。

旧洞重提,我能学到什么

而如何找到验证码这个类的路由满足条件呢?一个简单的方法,就是先打印出 self::$rules,所有已经注册的路由都会在这个静态变量里保存着,将这个变量打印出

旧洞重提,我能学到什么

如下图,可以看到,在 get 里注册了验证码的路由,这也就是 POC 中的 method 为什么是 get 的原因。

旧洞重提,我能学到什么

这样,利用验证码这个类的路由,所有条件都满足了。

POC如下:


POST:http://test/public/index.php?s=captcha/calc
_method=__construct&filter[]=system&method=get

旧洞重提,我能学到什么

到这,其实漏洞分析就结束了。先总结一下,首先一处疑似可能 RCE 的利用点,通过层层回溯,发现入口是在 Request 类的 param 方法,此处在反序列化 RCE 中也是可以利用的。而为了控制 param 方法的各项参数,需要透过 Request 类的 method 方法来触发 Request 的任意方法执行,当执行了 Request 类的 __construct 魔术方法时,通过变量覆盖完成了控制 pamra 方法的参数。上述的每个问题点在代码之中都显而易见,但点连成线,却是我没考虑到的。 

在完成漏洞成因的分析,那么剩下的就是寻找 parama 的触发点和满足一切条件来到触发点。所以寻找到验证码类只是为了满足条件的一环,而不是必须。那么在看到其他地方触发了 param 方法时,也是可以利用的。

旧洞重提,我能学到什么

比如,当开启了 debug 模式,就会记录路由信息,此时则会触发 param 方法,这时的 POC 就不依赖该类了。


POST:http://test/public/index.php?s=index/index&a=calc
_method=__construct&filter[]=system

旧洞重提,我能学到什么

未严格限制控制器引发RCE

这个漏洞流程也很简单,反思了一下自己在阅读源码的时候没思考到的点。ThinkPHP 在获取控制器名的时候没有严格控制,但是自己在阅读源码的时候出于惯性思维,没有想到可以用命名空间的方式实例化非控制器的类。简单的提一下这个漏洞的流程。

首先是在解析 url 路径的过程中,仅仅是使用了 / 做分隔符,来切割出一个路径数组。比如 index/think/App/action。这样切割出来的模块是 index,而控制器则是 thinkApp,操作是 action。而在 PHP 中是支持了命名空间的方式引用某个类。所以在这里自然而然的可将控制器定为 thinkApp 类。当然,不一定是要 App 类,也可以是其他的,但是该类可以执行 RCE。

旧洞重提,我能学到什么

而后续,并没有对控制器名进行处理,于是在如下代码中,就会实例化出一个 App 类的对象。

旧洞重提,我能学到什么

接着,后续就可以根据如下代码,执行 App 类中的任意方法,此时的 $method 是一个数组,含有两个元素,一个是 App 类对象,一个是 App 类定义的方法。

旧洞重提,我能学到什么

那选择 App 类中的哪个方法呢?

在 App 类中翻一番,可以看到 invokeFunction 可以通过反射的方式执行任意函数,并且因为参数绑定,参数可控,显然已经可以 RCE 了。

旧洞重提,我能学到什么

于是构造 POC


http://test/public/index.php?s=index/thinkapp/invokefunction
function=call_user_func_array&vars[0]=system&vars1=calc
 

旧洞重提,我能学到什么

总结

这两个漏洞已经公开一年多了,最近在阅读 ThinkPHP 的源码,在阅读到 Request 类的时候,发现这处疑似可利用的 RCE 的点,就想着进而分析一下这个历史漏洞。我将审计过程分为了两步:

第一分析漏洞点和漏洞成因,第二是如何满足条件,到达漏洞现场。

第一步很顺利,也能够轻易的分析出来。但是对于 ThinkPHP 路由不熟悉,无法理解如何寻找到验证码这个类并加以利用。当然如果利用已有的 POC 和动态调试,一路跟进自然能发现。那么如何在没有 POC 的基础下,如何分析出呢?此时似乎有些钻牛角尖,想在代码逻辑上强行分析,阅读代码许久无果,然后才意识到打印出一些变量来发现些蛛丝马迹(打印出了已注册的路由信息可以看到验证码类的路由)。而后理清整个漏洞流程的时候,才意识到验证码类只是利用的一个方式,还有其他不同的方式,当然需要环境的支持。再加深理解了 Request 类的作用(一次请求的所有信息都保存在一个 Request 对象之中)以及该类产生RCE的条件(控制 Request 的 $this->param 和 $this->filter),这个地方在反序列化的利用链中也是关键的一环。

再谈谈第二个漏洞。在阅读 ThinkPHP 源码的时候出于惯性思维,在获取控制器的代码处,第一反应到的 URL 是 indexindexindex,没考虑到在 PHP 中可使用命名空间。如果可以思考到这一点,后续则会相对容易些,但如何寻找到合适的类和合适的方法又是要细细分析。其中的一些坑,还需要动手调试才能体会,而且在不断调试的过程中,对框架机制逐渐熟悉,当然也意识到薄弱之处,比如还是无法完全理解 ThinkPHP 的自定义路由等等。

旧洞重提,我能学到什么

本文始发于微信公众号(信安之路):旧洞重提,我能学到什么

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年5月10日01:32:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   旧洞重提,我能学到什么http://cn-sec.com/archives/251414.html

发表评论

匿名网友 填写信息