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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
// 变量定义
$variable = "Hello World";
$number = 123;
$array = array(1, 2, 3);

// 条件语句
if ($condition) {
// 代码块
} else {
// 代码块
}

// 循环
for ($i = 0; $i < 10; $i++) {
echo $i;
}

// 函数定义
function myFunction($param) {
return $param . " processed";
}
?>

类与对象

类和对象基础概念

1.类(Class)

  • 就像是角色的设计模板
  • 模板上规定了角色应该有什么特征(属性)和能做什么(方法)
  • 但模板本身不是具体的角色,它只是描述角色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类就像一个人物设计模板
class Person {
// 属性:描述特征(像模板上的基本信息)
public $name; // 姓名
public $age; // 年龄

// 方法:描述行为(像模板上的能力说明)
public function introduce() {
echo "大家好,我叫{$this->name},今年{$this->age}岁";
}

public function growUp() {
$this->age += 1;
echo "{$this->name}长大了一岁,现在{$this->age}岁了";
}
}

2.对象(Object)

  • 对象是根据模板创建出来的具体角色
  • 每个角色都是独立的,有自己的姓名、年龄
  • 可以根据同一个模板创建很多个不同的角色
1
2
3
4
5
6
7
8
9
10
11
12
// 对象就像具体的游戏角色
$person1 = new Person("xt", 20); // 创建第一个角色
$person1->name = "xt"; // 角色叫xt
$person1->age = 20; // 角色20岁

$person2 = new Person("HDdss", 19); // 创建第二个角色
$person2->name = "HDdss"; // 角色叫HDdss
$person2->age = 19; // 角色19岁

// 两个角色互不影响
$person1->introduce(); // xt自我介绍
$person2->growUp(); // HDdss长大一岁

3.实例化(Instantiation)

  • 就是根据模板实际创建角色的过程
  • 使用 new 关键字来”创建”对象
1
2
3
// 实例化就是创建角色的过程
$person1 = new Person("xt", 20); // 创建第一个人
$person2 = new Person("HDdss", 19); // 创建第二个人

类和对象的作用

1.封装
把私密信息隐藏起来,只提供安全的访问方式:

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
<?php
class Person {
private $secretData; // 私有属性,外部不能直接访问(像私密数据)
public $name; //公共属性
public $age;

public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
$this->secretData = $this->generateSecretData(); // 内部生成私密数据
}

// 公共方法,提供安全访问
public function showBasicInfo() {
echo "姓名:{$this->name},年龄:{$this->age}";
}

public function verifyIdentity($id) {
// 安全验证后才返回信息
if ($this->checkPermission()) {
return "Data:{$this->secretData}";
}
return "权限不足";
}

private function generateSecretData() {
// 内部方法,生成Data
return "Data_" . rand(1000, 9999);
}

private function checkPermission() {
// 内部权限检查
return true;
}
}

$person = new Person("xt", 20);
$person->showBasicInfo(); // 可以调用公共方法
echo $person->verifyIdentity("1145"); // 通过安全方式验证

// 下面这句代码会报错,因为不能直接访问私有属性
// echo $person->secretData;

2.继承
子类继承父类的特性,还可以添加子类自己的特色:

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
<?php
// 父类:CTF选手
class CTFPlayer {
public $name;
public $age;

public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}

public function introduce() {
echo "我是{$this->name},今年{$this->age}岁";
}

public function practice() {
echo "{$this->name}正在训练";
}
}

// 子类:Web手,继承自CTFPlayer
class WebPlayer extends CTFPlayer {
public $specialty = "Web安全"; // 专长领域
public $tools = "BurpSuite"; // 常用工具

// 重写方法:Web手特有的自我介绍
public function introduce() {
echo "我是Web安全选手{$this->name},擅长{$this->specialty}";
}

// 新增方法:Web手特有的技能
public function phpSerialize() {
echo "{$this->name}正在练习PHP反序列化";
}

public function useTools() {
echo "{$this->name}正在使用{$this->tools}进行漏洞测试";
}
}

$webPlayer = new WebPlayer("xt", 20);
$webPlayer->introduce(); // 调用重写后的方法
$webPlayer->practice(); // 调用继承的方法
$webPlayer->phpSerialize(); // 调用特有方法
$webPlayer->useTools(); // 调用特有方法
?>

反序列化

为什么要序列化

序列化是将对象转换为可存储或传输的字符串的过程:

  • 数据持久化存储
  • 网络传输对象
  • 会话管理
  • 缓存数据
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Person{
public $name;
public $age;
public function __construct($name, $age){
$this->name = $name;
$this->age = $age;
}
}

$person = new Person('xt', 20);
echo serialize($person);
?>

反序列化漏洞的利用原理

当不可信的数据被反序列化时,攻击者可以:

  • 控制对象属性值
  • 触发魔术方法的执行
  • 构造恶意对象链实现代码执行

漏洞产生条件:

  • 用户输入直接传递给unserialize()
  • 存在可利用的类和方法

序列化利用函数

1
2
3
4
5
6
7
8
// 序列化
$data = serialize($object);

// 反序列化
$object = unserialize($serialized_data);

// 危险函数组合
unserialize($_POST['data']); //这下看懂了

魔术方法

魔术方法是PHP中一些特殊的方法,它们会在特定情况下自动被PHP调用,而不是由我们直接调用。这些方法都以双下划线(__)开头。

__construct

对象创建时自动调用

1
2
3
4
5
6
7
8
9
10
11
12
13
class 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
11
class 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
12
class 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
14
class 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
12
class 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
16
class 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
18
class 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
12
class 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
17
class 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对象