CTF-PHP反序列化漏洞1-基础知识
本专栏CTF基础入门系列打破
以往CTF速成或就题论题模式。采用系统讲解基础知识 入门题目练习 真题讲解方式
。让刚接触CTF的读者真正掌握CTF中各类型知识点,为后续自学或快速刷题备赛,打下坚实的基础~
目前ctf比赛,一般选择php作为首选语言,如读者不了解php的基本语法,请登录相关网站自学下基本语法即可,一般5-7天即可掌握基础。
1. 什么是PHP序列化和反序列化
1.1 基础概念
- PHP序列化是将一个PHP对象转换成一个字符串,以便在不同的应用程序之间传递和存储。
- 反序列化是将序列化的字符串转换回PHP对象。攻击者可以通过构造恶意的序列化字符串来触发代码执行,这就是PHP反序列化漏洞的本质。
- PHP序列化函数官方文档:https://www.php.net/manual/en/function.serialize.php
- PHP反序列化函数官方文档:https://www.php.net/manual/en/function.unserialize.php
1.2 基础知识
序列化是将 PHP 对象转换为可存储或传输的字符串的过程。序列化后的字符串可以保存到文件或通过网络传输到其他计算机,在需要时可以反序列化为原始对象。
序列化的基本原理是将 PHP 对象转换为一组字符串,其中包含对象的属性和变量。序列化后的字符串可以被反序列化为原始对象,从而重新创建对象。
PHP 序列化可以使用 PHP 内置的 serialize() 函数进行。例如,以下代码将一个 PHP 对象序列化为字符串:
$object = new MyClass();
$string = serialize($object);
在上面的代码中,$object 是一个 MyClass 类的实例,serialize() 函数将其序列化为一个字符串,存储在 $string 变量中。
反序列化可以使用 PHP 内置的 unserialize() 函数进行。例如,以下代码将一个序列化的字符串反序列化为 PHP 对象:
$string = 'O:7:"MyClass":2:{s:3:"foo";s:3:"bar";s:3:"baz";i:123;}';
$object = unserialize($string);
在上面的代码中,$string 是一个序列化的字符串,unserialize() 函数将其反序列化为一个 MyClass 类的实例,存储在 $object 变量中。
需要注意的是,PHP 序列化只能序列化 PHP 对象,不能序列化资源、闭包等其他类型的数据。另外,由于序列化后的字符串包含对象的私有属性和方法,因此在反序列化时需要确保对象的类定义已经加载到内存中。
简单来说,就是将一个php对象转化为字符串保存(序列化),方便传输到远端后,在远端再还原成对象的一个过程(反序列化)。
1.3 PHP反序列化漏洞的危害
PHP反序列化漏洞可以导致远程代码执行,攻击者可以通过构造恶意的序列化字符串,将任意代码注入到应用程序中,从而实现控制服务器的目的。
简单说就是构造恶意的字符串(序列化),这样远端还原对象时(反序列化),就把恶意的对象还原并执行了。
1.4 PHP反序列化漏洞的防御措施
防御PHP反序列化漏洞的方法有多种,其中最重要的是对用户输入进行过滤和验证。此外,还可以使用PHP内置的序列化函数进行序列化和反序列化,而不是使用第三方库。
2. 知识点讲解
首先我们先看一个完整的PHP序列化和反序列化的代码
CTF中往往会直接给出代码,需要分析代码编制恶意字符串
2.1 类的访问修饰符
上图中定义了一个类Tree,类中前三行分别出现了public、private、protected,分别是什么意思呢?下面我们就来详细介绍下~·
- 类内部:是指类定义的内部,即类名后大括号{ }内部。
- 类外部:是指类定义的外部内容,即类名后大括号{}之外的所有地方。
- 类成员的访问权限控制分为:内部访问(私有的private)。内部访问(受保护protected)和全部访问(公有public)。
2.1.1 public 公开的
公开的属性或函数,可在类内部、外部访问public $name='BMW'
public function XXX{}
2.1.2 protected 受保护的
受保护的属性或函数,只能在类及其子类、父类间内部访问。若想在外部访问,需要设置引用方法。protected $color='blue'
2.1.3 private 私有的
私有的属性或函数,只能在当前类的内部访问,若想在外部访问,需要设置引用方法。
比如上图中最后三个echo的调用,如下图可以看到,public可以正常调用,其余两个产生报错
2.2. 相关函数和重要知识点
2.2.1 基础定义
序列化的目的是方便对象的传输和存储。
- 序列化
指将一个实例化的对象从一个实例转换为一个简短的序列化字符串,这样便于保存对象,可以将序列化字节存储到数据库或者文本当中。
- 反序列化
是当需要的时候再通过反序列化将序列化字符串解析,获取保存的对象,直接调用,而不需要重新实例化一个类
在PHP应用中,序列化和反序列化一般用做缓存,比如session缓存,cookie等
2.2.2 相关函数及技巧知识点
serialize(mixed $value)
参数为需要序列化的对象、数组、字符串等。返回值类型为字符串,即序列化字符串。
unserialize(string $str): mixed
参数类型为字符串,也就是序列化字符串。返回值为反序列化得到的对象、数组、字符串等。
<?php
class Car{
public $name='BMW';
protected $color='blue';
private $size='large';
private $price;
function __construct(){
echo '序列化时调用构造方法<br>';
}
function __destruct(){
echo '反序列化时调用析构函数<br>';
}
function show(){
echo $this->name.'<br>';
echo $this->color.'<br>';
echo $this->size.'<br>';
echo 'price:"'.$this->price.'"<br>';
}
}
$myCar = new Car();
$o = serialize($myCar);
print_r($o);
print_r("\n");
print_r(urlencode($o));
print_r("\n");
$un_o = unserialize(urlencode($o));
print_r($un_o);
?>
// 序列化时调用构造方法
// O:3:"Car":4:{s:4:"name";s:3:"BMW";s:8:"*color";s:4:"blue";s:9:"Carsize";s:5:"large";s:10:"Carprice";N;}
// O:3:"Car":4:{s:4:"name";s:3:"BMW";s:8:" * color";s:4:"blue";s:9:" Car size";s:5:"large";s:10:" Car price";N;}
// 反序列化时调用析构函数
这段代码定义了一个名为Car的类
,包含公共属性$name
、受保护属性$color
、私有属性$size
和未定义初始值的私有属性$price
,以及构造函数__construct()
和析构函数__destruct()
和一个公共方法show()
,用于输出属性的值。
在代码中,首先创建了一个Car类的实例$myCar
,并将其序列化为字符串$o
,然后打印输出$o
和$o的URL编码形式。
$o=O:3:"Car":4:{s:4:"name";s:3:"BMW";s:8:"*color";s:4:"blue";s:9:"Carsize";s:5:"large";s:10:"Carprice";N;}
urlencode($o) = O:3:"Car":4:{s:4:"name";s:3:"BMW";s:8:" * color";s:4:"blue";s:9:" Car size";s:5:"large";s:10:" Car price";N;}
接着,将URL编码后的字符串$o反序列化为一个新的对象$un_o
,并打印输出$un_o。
在输出的过程中,构造函数__construct()被调用,输出序列化时调用构造方法,而析构函数__destruct()在反序列化时被调用,输出反序列化时调用析构函数。在调用show()方法时,只有公共属性$name
和受保护属性$color
被输出,而私有属性$size
的值无法输出。
PHP序列化字符串的格式如下:
对象类型:长度:“类名”:类中变量的个数:{类型:长度:“值”;类型:长度:“值”;......}
这里的长度是指字符串长度 o表示对象,a表示数组,s表示字符,i表示数字