最近一边划水一边看了点审计,看完了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 as $key => $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的值 然后赋上去

分类: 技术

4 条评论

imagin · 三月 25, 2020 9:47 上午

催更!

    P3rh4ps · 三月 25, 2020 11:33 上午

    卧槽 你这次还挂代理上了

Mrkaixin · 三月 26, 2020 6:25 上午

催更

123 · 四月 13, 2020 4:13 下午

alert(1)

发表评论

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