PHP序列化
什么是PHP序列化
serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象
通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击
<?php
highlight_file(__FILE__);
class sunset{
public $flag='flag{asdadasd}';
public $name='makabaka';
public $age='18';
}
$ctfer=new sunset(); //实例化一个对象
echo serialize($ctfer);
?>
- 返回结果
O:6:"sunset":3:{s:4:"flag";s:14:"flag{asdadasd}";s:4:"name";s:8:"makabaka";s:3:"age";s:2:"18";}
- O代表对象,这里是序列化的一个对象,要序列化数组的就用A
- 6表示的是类的长度
- sunset表示对是类名
- 3表示类里面有3个属性也称为变量
- s表示字符串的长度
这里的flag表示属性
- 比如
s:4:"flag"
这里表示的是 flag属性名(变量名)为4个字符串长度 字符串 属性长度 属性值
什么是反序列化
这里是把上面序列化之后返回的数据进行反序列化
$str='O:6:"sunset":3:{s:4:"flag";s:14:"flag{asdadasd}";s:4:"name";s:8:"makabaka";s:3:"age";s:2:"18";}';
$a=unserialize($str);
var_dump($a);
PHP中public、protected、private的区别对比
public
public修饰的属性和方法可以在任何地方被访问,包括类的内部、子类和外部代码。
示例:
<?php
class Person {
public $name;
public function sayHello() {
echo "Hello!";
}
}
$person = new Person();
echo $person->name; // 可以直接访问 public 属性
$person->sayHello(); // 可以直接调用 public 方法
?>
protected
protected修饰的属性和方法只能在当前类及其子类中被访问,外部的代码访问不了
<?php
highlight_file(__FILE__);
class Person {
protected $name;
protected function sayHello() {
echo "Hello!";
}
}
class Student extends Person {
public function showName() {
echo $this->name; // 子类可以访问 protected 属性
$this->sayHello(); // 子类可以调用 protected 方法
}
}
$student = new Student();
$student->showName(); // 可以访问父类的 protected 属性和方法
echo $student->name; // 外部代码不能访问 protected 属性 会显示错误
$student->sayHello(); // 外部代码不能调用 protected 方法 会显示错误
?>
private
private修饰的属性和方法只能在当前类中被访问,子类和外部代码不能访问。
<?php
highlight_file(__FILE__);
class Person {
private $name;
private function sayHello() {
echo "Hello!";
}
}
class Student extends Person {
public function showName() {
echo $this->name; // 子类不能访问父类的 private 属性
$this->sayHello(); // 子类不能调用父类的 private 方法
}
}
$person = new Person();
echo $person->name; // 外部代码不能访问 private 属性 会发生报错
$person->sayHello(); // 外部代码不能调用 private 方法 会发生报错
?>
总结
魔术方法
在利用对PHP反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里有无敏感操作来进行利用。
常见的魔术方法
__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
__sleep()
__sleep()
方法是 PHP 中的一个魔术方法(magic method),用于在对象被序列化(serialized)时触发。在这个方法中,你可以指定哪些属性需要被序列化,哪些属性不需要被序列化。具体来说,当调用
serialize()
函数将一个对象序列化时,PHP 会先自动调用对象的__sleep()
方法,该方法需要返回一个数组,包含需要被序列化的属性名。然后 PHP 会将这些属性序列化成字符串。
假设有一个
User
类,它有一个私有属性$password
,你不希望在序列化对象时将密码属性暴露出来。那么你可以在User
类中实现__sleep()
方法
<?php
highlight_file(__FILE__);
class User {
private $username;
private $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
public function __sleep() {
return array('username');
}
}
$user = new User('john', '123456');
$serialized = serialize($user);
echo $serialized;
在上面的例子中,User
类的 __sleep()
方法返回了一个只包含 $username
属性名的数组,这意味着在序列化对象时,只有用户名会被序列化。如果你运行上面的代码,你会看到输出的序列化字符串只包含了 username
属性的值。
关于序列化后的字符串中 s:14:"Userusername";s:4:"john";
中的 s:14
,实际上是指 “Userusername” 的长度为 12 个字符,而不是 10 或 14 个字符。这是因为在 PHP 序列化字符串中,每个字符串的前面都会有一个类似 s:6:
的字符串长度标识,表示该字符串的长度为 6 个字符。这个字符串长度标识包括 s:
、冒号和数字长度,加起来占用了 4 个字符,所以实际上字符串长度标识的长度为字符串长度加 2。在您的输出结果中,s:14:"Userusername";s:4:"john";
中的
__wakeup()
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源 而
wakeup()
用于在从字符串反序列化为对象时自动调用。一个 PHP 对象被序列化成字符串并存储在文件、数据库或者通过网络传输时,我们可以使用unserialize()
函数将其反序列化为一个 PHP 对象。在这个过程中,PHP 会自动调用该对象的__wakeup()
方法,对其进行初始化。
__wakeup()
方法的作用是对一个对象进行一些必要的初始化操作。例如,如果一个对象中包含了一些需要进行身份验证的属性,那么在从字符串反序列化为对象时,就可以在__wakeup()
方法中进行身份验证。或者如果一个对象中包含了一些需要在每次初始化时计算的属性,也可以在__wakeup()
方法中进行计算
示例1:
<?php
highlight_file(__FILE__);
class User {
private $username;
private $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
public function __sleep() {
return array('username', 'password');
}
public function __wakeup() {
if (!$this->authenticate()) {
throw new Exception("Authentication failed");
}
}
private function authenticate() {
// 进行身份验证
}
}
$user = new User('john', '123456');
$serialized = serialize($user);
$unserialized = unserialize($serialized);
在上面的示例中User
类实现了 __sleep()
和 __wakeup()
方法。__sleep()
方法返回了一个包含 username
和 password
属性名的数组,表示只有这两个属性需要被序列化。__wakeup()
方法会调用 authenticate()
方法进行身份验证。如果身份验证失败,则会抛出一个异常。
实例2:
<?php
highlight_file(__FILE__);
class Caiji{
public function __construct($ID, $sex, $age){
$this->ID = $ID;
$this->sex = $sex;
$this->age = $age;
$this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
}
public function getInfo(){
echo $this->info . '<br>';
}
/**
* serialize前调用 用于删选需要被序列化存储的成员变量
* @return array [description]
*/
public function __sleep(){
echo __METHOD__ . '<br>';