PHP的序列化和反序列化入门

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个字符串长度 字符串 属性长度 属性值

什么是反序列化

这里是把上面序列化之后返回的数据进行反序列化

image-20230222124032420

$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 方法
?>

image-20230223095619107

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 方法 会显示错误
?>

image-20230223100736605

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 方法 会发生报错

?>

image-20230223101020415

总结

魔术方法

在利用对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;

image-20230222190827201

在上面的例子中,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() 方法返回了一个包含 usernamepassword 属性名的数组,表示只有这两个属性需要被序列化。__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>';
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值