PHP反序列化学习
PHP基础与反序列化漏洞
XDSEC Web第一次组会
主讲:fifker
PHP基本知识
PHP的作用/应用简介
PHP(Hypertext Preprocessor)是一种流行的服务器端脚本语言,主要用于:
- 动态网页开发
- 命令行脚本编写
- 桌面应用程序开发
- Web服务端开发
PHP的部署
- 命令行
1
php -S 127.0.0.1:7777 -t /var/www/html/
- PhpStorm
- phpstudy
PHP基础语法
1 |
|
类与对象
类和对象基础概念
1.类(Class)
- 类就像是角色的设计模板
- 模板上规定了角色应该有什么特征(属性)和能做什么(方法)
- 但模板本身不是具体的角色,它只是描述角色
1 | // 类就像一个人物设计模板 |
2.对象(Object)
- 对象是根据模板创建出来的具体角色
- 每个角色都是独立的,有自己的姓名、年龄
- 可以根据同一个模板创建很多个不同的角色
1 | // 对象就像具体的游戏角色 |
3.实例化(Instantiation)
- 就是根据模板实际创建角色的过程
- 使用
new关键字来”创建”对象
1 | // 实例化就是创建角色的过程 |
类和对象的作用
1.封装
把私密信息隐藏起来,只提供安全的访问方式:
1 |
|
2.继承
子类继承父类的特性,还可以添加子类自己的特色:
1 |
|
反序列化
为什么要序列化
序列化是将对象转换为可存储或传输的字符串的过程:
- 数据持久化存储
- 网络传输对象
- 会话管理
- 缓存数据
1 |
|
反序列化漏洞的利用原理
当不可信的数据被反序列化时,攻击者可以:
- 控制对象属性值
- 触发魔术方法的执行
- 构造恶意对象链实现代码执行
漏洞产生条件:
- 用户输入直接传递给unserialize()
- 存在可利用的类和方法
序列化利用函数
1 | // 序列化 |
魔术方法
魔术方法是PHP中一些特殊的方法,它们会在特定情况下自动被PHP调用,而不是由我们直接调用。这些方法都以双下划线(__)开头。
__construct
对象创建时自动调用1
2
3
4
5
6
7
8
9
10
11
12
13class Person {
public $name;
public $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
echo "创建了Person对象: {$this->name}";
}
}
$person = new Person("xt", 20);
// 输出: 创建了Person对象: xt
__destruct
对象销毁时自动调用1
2
3
4
5
6
7
8
9
10
11class Person {
public $name;
public $age;
public function __destruct() {
echo "Person对象被销毁: {$this->name}";
}
}
$person = new Person("xt", 20);
// 脚本结束时输出: Person对象被销毁: xt
__wakeup
反序列化时自动调用1
2
3
4
5
6
7
8
9
10
11
12class Person {
public $name;
public $age;
public function __wakeup() {
echo "Person对象从序列化恢复: {$this->name}";
}
}
$data = 'O:6:"Person":2:{s:4:"name";s:6:"xt";s:3:"age";i:20;}';
$person = unserialize($data);
// 输出: Person对象从序列化恢复: xt
__toString
对象被当作字符串使用时调用1
2
3
4
5
6
7
8
9
10
11
12
13
14class Person {
public $name;
public $age;
public function __toString() {
return "Person: {$this->name}, {$this->age}岁";
}
}
$person = new Person();
$person->name = "xt";
$person->age = 20;
echo $person;
// 输出: Person: xt, 20岁
__call
调用不可访问的方法时触发1
2
3
4
5
6
7
8
9
10
11
12class Person {
public $name;
public $age;
public function __call($method, $args) {
echo "调用了不存在的方法: {$method}";
}
}
$person = new Person();
$person->nonExistentMethod();
// 输出: 调用了不存在的方法: nonExistentMethod
get / set
访问不可访问的属性时触发1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Person {
private $data = [];
public function __get($name) {
return $this->data[$name] ?? "属性{$name}不存在";
}
public function __set($name, $value) {
$this->data[$name] = $value;
}
}
$person = new Person();
$person->name = "xt"; // 触发__set
echo $person->name; // 触发__get,输出: xt
echo $person->age; // 触发__get,输出: 属性age不存在
isset / unset
对不可访问属性使用isset()或unset()时触发1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Person {
private $data = [];
public function __isset($name) {
echo "检查属性: {$name}";
return isset($this->data[$name]);
}
public function __unset($name) {
echo "删除属性: {$name}";
unset($this->data[$name]);
}
}
$person = new Person();
$person->name = "xt";
isset($person->name); // 输出: 检查属性: name
unset($person->name); // 输出: 删除属性: name
__invoke
对象被当作函数调用时触发1
2
3
4
5
6
7
8
9
10
11
12class Person {
public $name;
public $age;
public function __invoke($param) {
echo "Person对象被作为函数调用: {$this->name} - {$param}";
}
}
$person = new Person();
$person->name = "xt";
$person("hello"); // 输出: Person对象被作为函数调用: xt - hello
__sleep
序列化时调用,返回需要序列化的属性名数组1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Person {
public $name;
public $age;
private $password = "secret";
public function __sleep() {
echo "准备序列化Person对象";
// 只序列化name和age,不序列化password
return ['name', 'age'];
}
}
$person = new Person();
$person->name = "xt";
$person->age = 20;
$serialized = serialize($person);
// 输出: 准备序列化Person对象