最近一边划水一边看了点审计,看完了tp文档,然后今天大概审了下tp5.0.14的链子
不会放exp,只写挖掘的流程,会尽可能还原我踩得坑。不得不说框架的反序列化跟平时ctf做的那些不是一个级别的,5.0.x的链子貌似还是相对比较复杂的,按朋友的话是我上来就选了个最难的- -也确实审的有点自闭。简单写一写自己的心路历程 也算安慰下自己,假装今天没有荒废

环境部署

composer一把梭

然后在public目录写个反序列化payload的

完事

phpstorm+xdebug配置

虽然我不习惯打断点 不过还是自己配了一下

chrome装个debug插件 phpstorm监听 访问走到断点就会自动触发调试

顺便开了下框架的debug模式

有点大。。

链子

因为我是第一次审框架的链子 而且整个框架只有四个文件存在__destruct 我就自己一点点审了

我会把自己审计的整个流程都详细写出来 之后再审别的版本应该就会直接写结果了

跟一下removeFiles的定义

这个file_exists是可以触发一个__tostring

全局搜__tostring

跟进toJson

这里调用了一个toArray

再找下toArray

写了个闭包函数 如果value是Model类的话 调用Model类的toArray方法 可以当跳板跳到Model类

我看了下Model类,然后发现同样存在__tostring toJson和toArray

那当然是直接走Model类了 跳个毛线跳

都是一模一样的 只是Model类对toArray做了具体的实现

    public function toArray()
    {
        item    = [];visible = [];
        hidden  = [];data = array_merge(this->data,this->relation);

        // 过滤属性
        if (!empty(this->visible)) {array = this->parseAttr(this->visible, visible);data  = array_intersect_key(data, array_flip(array));
        } elseif (!empty(this->hidden)) {array = this->parseAttr(this->hidden, hidden, false);data  = array_diff_key(data, array_flip(array));
        }

        foreach (data askey => val) {
            if (val instanceof Model || val instanceof ModelCollection) {
                // 关联模型对象item[key] =this->subToArray(val,visible, hidden,key);
            } elseif (is_array(val) && reset(val) instanceof Model) {
                // 关联模型数据集
                arr = [];
                foreach (val as k =>value) {
                    arr[k] = this->subToArray(value, visible,hidden, key);
                }item[key] =arr;
            } else {
                // 模型属性
                item[key] = this->getAttr(key);
            }
        }
        // 追加属性(必须定义获取器)
        if (!empty(this->append)) {
            foreach (this->append as key =>name) {
                if (is_array(name)) {
                    // 追加关联对象属性relation   = this->getAttr(key);
                    item[key] = relation->append(name)->toArray();
                } elseif (strpos(name, '.')) {
                    list(key, attr) = explode('.',name);
                    // 追加关联对象属性
                    relation   =this->getAttr(key);item[key] =relation->append([attr])->toArray();
                } else {relation = Loader::parseName(name, 1, false);
                    if (method_exists(this, relation)) {modelRelation = this->relation();//getError
                        value         =this->getRelationData(modelRelation);
                         //value需要是Output对象
                        if (method_exists(modelRelation, 'getBindAttr')) {
                            bindAttr =modelRelation->getBindAttr();
                            if (bindAttr) {
                                foreach (bindAttr as key =>attr) {
                                    key = is_numeric(key) ? attr :key;
                                    if (isset(this->data[key])) {
                                        throw new Exception('bind attr has exists:' . key);
                                    } else {item[key] =value ? value->getAttr(attr) : null;
                                    }
                                }
                                continue;
                            }
                        }
                        item[name] = value;
                    } else {item[name] =this->getAttr(name);
                    }
                }
            }
        }
        return !empty(item) ? $item : [];
    }

有点长 截图放不下

这里有一个对象和参数都可控的调用引起了我的注意

这是一个__call方法的绝佳调用

不过由于还不知道有没有可利用的_call方法 我决定先看有没有可利用的\_call再决定是不是要构造

Output类里有可利用的__call

简单跟了下发现会走到write 可以调用任意类的write

再找一个可利用的set

这里的set第一次调用时写入的内容是不可控的 可控的在setTagItem的第二次调用中

讲一下比较难构造的点

toArray中的构造:前半段可以不管,直接看append 的处理

最开始的分支要走到else里 append数组中的值就不能是数组 也不能存在.

然后看else中的代码

$relation = Loader::parseName($name, 1, false);
                    if (method_exists($this, $relation)) {
                        $modelRelation = $this->$relation();
                        $value= $this->getRelationData($modelRelation);

                        if (method_exists($modelRelation, 'getBindAttr')) {
                            $bindAttr = $modelRelation->getBindAttr();
                            if ($bindAttr) {
                                foreach ($bindAttr as $key => $attr) {
                                    $key = is_numeric($key) ? $attr : $key;
                                    if (isset($this->data[$key])) {
                                        throw new Exception('bind attr has exists:' . $key);
                                    } else {
                                        $item[$key] = $value ? $value->getAttr($attr) : null;
                                    }

                            }

$value来自getRelationData处理之后的$modelRelation 而$modelRelation是$relation被Loader::parseName处理之后作为方法名的返回值 先康康Loader::parseName是怎么写的

type为1 把大写字母中间加上_再转小写 应该是用来驼峰法之类的

直接小写就完了

也就是说$modelRelation是一个Model任意方法的返回值 再看看getRelationData是怎么处理的

这里要求参数是Relation类 所有要找一个可以返回Realtion对象的方法

getError一把梭

php调用方法是不区分大小写的 所以使用geterror防止被parseName替换掉

所以还要再加一个Relation类

哦豁 又是个抽象类 找一个继承它并且还有getBindAttr方法的

hasOne

这个就很好构造了 显然是可以把$value赋值成任何对象的(只要parent跟Relation的model属性属于同一个类就可以了 要注意的是get_class返回的是字符串不是实例 可以自己打个断点看下get_class的值 然后赋上去

Categories: 技术

3 Comments

imagin · 3月 25, 2020 at 9:47 上午

催更!

    P3rh4ps · 3月 25, 2020 at 11:33 上午

    卧槽 你这次还挂代理上了

Mrkaixin · 3月 26, 2020 at 6:25 上午

催更

发表评论

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注