0%

bestphp's revenge

难难难,自闭自闭自闭。

题目给了源码:
index.php

1
2
3
4
5
6
7
8
9
10
11
12
 <?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

1
2
3
4
5
6
7
only localhost can get flag!session_start();
echo 'only localhost can get flag!';
$flag = 'flag{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$_SESSION['flag'] = $flag;
}
only localhost can get flag!

看到flag.php的内容我们知道需要用ssrf了。我们需要:ssrf访问 127.0.0.1/flag.php 然后再利用变量覆盖把SESSION中的flag打印出来。

call_user_func 函数

我们从index.php中,发现call_user_func函数。

它会把第一个参数作为回调函数,其余参数当作为回调函数的参数。

假如我们第一参数传入的是数组,会把数组的第一个值当作类名,第二个值当作方法进行回调。

call_user_func 函数不止可以调用自定义函数、类,也可以调用php内置函数、内置类 如extract

PHP中SESSION反序列化机制

以前也接触过:
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。
在php.ini中存在三项配置项:

1
2
3
session.save_path=""   --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认是php(5.5.4后改为php_serialize)

session.serialize_handler存在以下几种:

1
2
3
php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值
php 键名+竖线(|)+经过serialize()函数处理过的值
php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化

在PHP中默认使用的是PHP引擎(5.5.4后改为php_serialize),如果要修改为其他的引擎,只需要添加代码ini_set(‘session.serialize_handler’, ‘需要设置的引擎’);。
当序列化的引擎和反序列化的引擎不一致时,就可以利用引擎之间的差异产生序列化注入漏洞。

构造SSRF之SoapClient类

SoapClient是php内置的类,当__call方法被触发后(调用不存在方法),它可以发送HTTP和HTTPS请求。该类的构造函数如下:

1
public SoapClient :: SoapClient (mixed $wsdl [,array $options ])

第一个参数是用来指明是否是wsdl模式。

第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。

构造SSRF之 CRLF Injection漏洞

CRLF是”回车+换行”(\r\n)的简称。在HTTP协议中,HTTPHeader与HTTPBody是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLFInjection又叫HTTPResponseSplitting,简称HRS。
我们要让服务器去访问flag.php,且把flag存放在session里,那么我们就一定需要携带一个cookie去访问它。但是SoapClient这个类,好像没有指定cookie的接口,所以,我们就可以在user_agent里面,加上一个\r\n,然后再加上一个cookie,就达到了我们的目的。

解题步骤

第一步,覆盖序列化引擎并将构造的Soap类序列化字符串写入session文件。
soap构造脚本:

1
2
3
4
5
6
7
8
9
<?php
$target='http://127.0.0.1/flag.php';
$b = new SoapClient(null,array('location' => $target,
'user_agent' => "AAA:BBB\r\n" .
"Cookie:PHPSESSID=66666666",
'uri' => "http://127.0.0.1/"));

$se = (serialize($b));
echo "|".$se;


第二步,我们覆盖变量b,利用call_user_func调用SoapClient类中的不存在方法,触发__call方法,执行ssrf。并获得访问flag.php的PHPSESSID。

接着我们访问我们设置的cookie值就可以得到flag。

解析 payload

  1. 第一步,f传的值和post的值使其使用php_serialize引擎。而name的值就是将我们的name值以php_serialize引擎的格式存储起来。
  2. 这次发的请求使用的是默认的php引擎,我们f传值和post传值来使call_user_func($b, $a);变成call_user_func($a);而$a 为一个数组且第一个值就是我们传入的SoapClient作为类,而第二个值welcome_to_the_lctf2018为方法,很显然没这个方法从而调用SoapClient的__call函数、执行ssrf。
  3. 最后就是已我们设置的cookie去访问了,它会返回$_SESSION而此时我们的flag已经在里面了。

参考链接:
https://skysec.top/2018/11/17/2018-Xctf%20Final&LCTF-Bestphp/#SoapClient
https://www.smi1e.top/lctf2018-bestphps-revenge-%E8%AF%A6%E7%BB%86%E9%A2%98%E8%A7%A3/


-------------本文结束感谢您的阅读-------------