环境搭建基础可以参考SQL注入篇和官方文档
代码执行1
通过传参可以调用任意类方法,调用一些具有回调功能的函数时可以导致代码执行。
影响版本:
- 5.0.7<=ThinkPHP<=5.0.22
- 5.1.0<=ThinkPHP<=5.1.30
1 | http://localhost/tpdemo/public/index.php?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id |
漏洞分析
5.0.23版本更新说明中表示包含一个安全更新,具体看到改进控制器获取这个commit

加了个限制大小写字母的过滤。从官方文档可以知道,获取控制器的方式取决于用的哪种路由模式,ThinkPHP默认无强制路由、支持兼容模式,SQL注入篇中都是用的?s=/模块/控制器/方法这种Payload,此处可以合理猜测能够调用到危险方法。
将源码更新为5.0.22,直接全局搜索eval、assert和system这类赤果果的关键词基本没得搞头,但是搜回调类、反射类的函数就会眼前一亮。

以reflect为例搜到的App类(thinkphp/library/think/App.php)第一条结果就是一个静态invokeFunction方法,invokeArgs方法类似call_user_func_array函数,只要$function和$args可控就能实现控制任意函数和参数代码执行了。跟进self::bindParams方法可以看到它的作用就是获取传入的参数,通过完全限定名称的命名空间调用并无脑传参就行了。

5.1版本的利用方法类似而且能利用的类比5.0更多,官方正则判断的修复方式就是卡了命名空间的逃逸。
$_SERVER['PATH_INFO']会将\转为/
代码执行2
影响版本:
- 5.0.0<=ThinkPHP5<=5.0.23
- 5.1.0<=ThinkPHP<=5.1.30
1 | # ThinkPHP <= 5.0.13 |
漏洞分析
5.0.24版本更新说明中表示包含一个安全更新,具体看到改进Request类这个commit

调用Request类方法前做了白名单判断,猜测漏洞可能是能调用当前类任意方法,找到Config::get('var_method')对应的值为_method。

回顾一下SQL注入篇中说过凡是使用框架提供的请求变量获取方法(Request类param方法及input助手函数),都会经过这个filterExp方法的过滤,其中的filterExp方法是被filterValue方法拉起调用的,而filterValue方法中就存在敏感函数call_user_func:

全局搜索对应的filterValue方法,看到可以由824行的cookie方法或是由994行的input方法触发(但是似乎框架默认逻辑没有用到cookie方法)。

跟进getFilter方法后看上去影响不大先不管,回来继续向上跟进array_walk_recursive函数传递的第一和第三个参数,进而寻找调用了input方法的地方(->input\(|::input\():

这个构造函数简直来得不要太妙(是个伏笔2333):如果当前类中的属性名有与$options数组中键名相同的,就会被覆盖为相应的键值,并且给$this->input属性赋值了完全可控的php://input。
有很多地方调用了input方法,先看下Request类的param方法:

出现了被更新白名单的method方法,至此利用链的链尾已经基本清晰:
?->param->method、input->filterValue->call_user_func
接下来需要思考如何通过Request类的某个方法修改默认为空的$filter的值呢?刚才那个构造函数刚好可以实现对$this->filter变量覆盖!也就是通过$_POST传入_method=__construct&filter[]=system。搞定了一个参数,继续想办法搞定另外一个参数:

继续通过变量覆盖控制$this->get或者$this->route的值,就能直接进到input方法中。也就是继续通过$_POST传入&get[]=whoami或是&route[]=whoami,此时如果'app_debug' => true,就可以直接看到命令执行结果:

这也印证了此时的param方法确实被框架调用了,但是一旦关掉app_debug就会发现并不能RCE了 T^T,显然事情没这么简单,我们还是得继续老实向前分析调用栈。动态调试一下看看调用栈里是谁翻了param方法的牌子:

self::$debug就是框架从配置文件中加载的值,所以关掉app_debug就不会调用到param方法了(淦)。那还有没有办法调用到呢?全局搜索可以看到当App类的exec方法中$dispatch['type']为controller或是method时就可以。

于是继续跟进方法调用和变量传递:



check方法里面有点复杂。。。马后炮一下直接先看parseRule方法:

需要$route为\或者@,继续回去跟进变量传递:

TP5完整版或是通过composer require topthink/think-captcha 1.*安装的验证码扩展,会在vendor/topthink/think-captcha/src/helper.php中注册一条get路由。由于此处$method是通过$request->method()获取到的,所以能够通过$_POST传入&method=get间接对其进行变量覆盖。
