LCTF2018——bestphp’s revenge
源码:
1 |
|
1 |
|
###思路
- 通过SSRF,本地访问flag.php从而满足
$_SERVER["REMOTE_ADDR"]==="127.0.0.1"
得到flag
反序列化机制
- 当序列化引擎与反序列化引擎不一致时,会发生一些问题
- 不同引擎的存储方式:
- php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
- php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
- php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
- 用php引擎进行序列化:
1 |
|
结果为:
可以看到,服务器端保存的session的文件名,就是用户访问时的PHPSESSID再加上前缀。
反序列化的结果为
name|s:4:"test"
用php_serialize引擎序列化:
如果精心构造,以php_serialize的方式序列化session,再以php的方式反序列化session,即可反序列化内置类soapclient
Soapclient类
通过soapclient类,可以具备SSRF能力
不难想到要构造一个soapclient类
而通过session改变序列化引擎,可以得到目的
大致思路,通过call_user_func,设置序列化引擎为
php_serialize
,将session以这种引擎序列化的值保存,而默认的序列化引擎为php
,详情参考这篇文章:PHP中SESSION反序列化机制先构造soap类:
1
2
3
4
5
6
7
8
9
10
11
//soap构造脚本
$target = 'http://127.0.0.1/baby_revenge/flag.php';
$b = new SoapClient(null,array('location' => $target,
'user_agent' => "AAA:BBB\r\n"."Cookie:PHPSESSID=7av36g5kq1rv9teofsboc3a046",
'uri' => 'http://127.0.0.1/baby_revenge/index.php'));
$se = serialize($b);
echo $se."<br>";
echo urlencode($se);这里user_agent要这样构造,是因为要让服务器本地访问flag.php的时候以你的session去访问,所以这里还考了一个
CRLF
接着通过call_user_func函数,设置session_start的引擎为php_serialize,再将我们构造的soap类传入:
可以看到已经成功在session中写入我们构造的变量
接着我们通过extract方法,变量覆盖原先的
$b
,使之变为call_user_func
,让name=Soapclient
,这样Soapclient类会调用一个不存在的方法,从而调用__call函数来触发ssrf
- 这是因为
name=Soapclient
后,经过$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
,$a = {'Soapclient','welcome_to_the_lctf2018'}
,之后调用call_user_func函数,相当于:call_user_func({'Soapclient','welcome_to_the_lctf2018'})
,当call_user_func的参数为数组时,代表调用类的方法,而Soapclient类并没有一个叫welcome_to_the_lctf2018
的方法,因此会调用call函数,触发SSRF