PHP序列化与反序列化

序列化

不同类型的序列化表示

<?php
$a = "string";
$b = 10;
$c = array();
$d = true;
$e = null;
$f = array($a,$b,$c,$d,$e);
foreach($f as $ff){
    $w = serialize($ff);
    var_dump($w) ;
    // $p =  unserialize($w);
    // var_dump($p);
}
var_dump(serialize($f));
?>
值得注意的是,数组序列化对象带有下标,类型为Integer

对象序列化

<?php
class Site {
  /* 成员变量 */
  var $url;
  var $title;

/* 成员函数 */
function setUrl($par){
$this->url = $par;
}

function getUrl(){
echo $this->url . PHP_EOL;
}

function setTitle($par){
$this->title = $par;
}

function getTitle(){
echo $this->title . PHP_EOL;
}
}
$s = new Site();
var_dump(serialize($s));
//string(41) "O:4:"Site":2:{s:3:"url";N;s:5:"title";N;}"

$s->url = 'http://www.iwonder.run';
$s->title = "Cdr's Blog";
var_dump(serialize($s));
//
string(85) "O:4:"Site":2:{s:3:"url";s:22:"http://www.iwonder.run";s:5:"title";s:10:"Cdr's Blog";}"

?>


可见,对于类对象的序列化,函数没有体现出来。规律可总结如下:

  • String : s:size:value;
  • Integer : i:value;
  • Boolean : b:value;(保存1或0)
  • Null : N;
  • Array : a:size:{key definition;value definition;(repeated per element)}
  • Object : O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}

不同修饰符修饰修饰的变量

<?php

class test
{
public $flag;
public function set_flag($flag)
{
$this->flag = $flag;
}
public function get_flag($flag)
{
return $this->flag;
}
}
$test = new test();
$test->$flag = 'flag';
var_dump(serialize($test));
?>


①修饰符为public时:string(46) "O:4:"test":2:{s:4:"flag";N;s:0:"";s:4:"flag";}"

②修饰符为protected时:string(49) "O:4:"test":2:{s:7:"

③修饰符为private时:string(53) "O:4:"test":2:{s:10:"

不同修饰符下成员名称长度是不同的。实际上,

  1. 受protected修饰的成员,序列化时:\00 * \00 [成员](4+3)
  2. 受private修饰的私有成员,序列化时: \00 [私有成员所在类名] \00 [私有成员](4+4+2)
  3. "\00"代表ASCII为0的值,即空字节," * " 必不可少。

序列化后的类型标识

a - array               b - boolean 
d - double              i - integer
o - common object       r - reference
s - string              C - custom object
O - class               N - null
R - pointer reference   U - unicode string

反序列化

serialize()将一个对象转换成一个字符串,而unserialize()将字符串还原为一个对象。

魔术方法速览

  1. __construct 在创建对象时候初始化对象,一般用于对变量赋初值
  2. __destruct 和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用,即对象被销毁时触发
  3. __toString 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串
  4. __wakeup() 使用unserialize时触发,反序列化恢复对象之前调用该方法
  5. __sleep() 使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
  6. __call() 在对象上下文中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
  7. __callStatic() 在静态上下文中调用不可访问的方法时触发
  8. __get() 用于从不可访问的属性读取数据,即在调用私有属性的时候会自动执行
  9. __set() 用于将数据写入不可访问的属性
  10. __isset() 在不可访问的属性上调用isset()或empty()触发
  11. __unset() 在不可访问的属性上使用unset()时触发
  12. __invoke() 当脚本尝试将对象调用为函数时触发

__construct构造函数

在一个类中只能声明一个构造方法,而是只有在每次创建对象的时候都会去调用一次构造方法,不能主动的调用这个方法,所以通常用它执行一些有用的初始化任务。该方法无返回值。

1.构造函数实例化类的时候会自动调用。

2.子类没有构造函数,会直接调用父类的构造涵数, 继承父类的属性和方法。

3.子类和父类都有构造函数,实例子类时不会自动调用父类构造函数,只会调用子类自己的构造函数。用 parent::__construct();可以调用父类的构造函数。

__destruct()析构函数

与构造方法对应的就是析构方法,析构方法允许在销毁一个类之前执行的一些操作或完成一些功能,比如说关闭文件、释放结果集等。析构函数不能带有任何参数 。

 

  1. 和构造方法一样,PHP 不会在本类中自动的调用父类的析构方法。要执行父类的析构方法,必须在子类的析构方法体中手动调用 parent::__destruct() 。
  2. 试图在析构函数中抛出一个异常会导致致命错误。

__toString()函数

 

__toString触发条件:

  1. echo ($obj) / print($obj) 打印时
  2. 字符串连接时 、格式化字符串时 、与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
  3. 格式化SQL语句,绑定参数时 数组中有字符串时
<?php
class toString_demo
{
    private $test1 = 'test1';
    public function __construct($test)
    {
        $this->test1 = $test;
    }
    public function __destruct()
    {
        // TODO: Implement __destruct() method.
        print "__destruct:";
        print $this->test1;
        print "n";
    }
    public function __wakeup()
    {
        // TODO: Implement __wakeup() method.
        print "__wakeup:";
        $this->test1 = "wakeup";
        print $this->test1."n";
    }
    public function __toString()
    {
        // TODO: Implement __toString() method.
        print "__toString:";
        $this->test1 = "tosTRING";
        return $this->test1."n";
    }
}
$a = new toString_demo("demo");
$b = serialize($a);
$c = unserialize($b);
print $c;
结果为:
__wakeup:wakeup  //序列化时启动__wakeup
__toString:tosTRING  //__toString方法处有返回值,实现调用
__destruct:tosTRING  //销毁对象$a
__destruct:demo  //销毁对象$c
当反序列化后的对象在经过php字符串函数时,都会执行__toString方法,比如strlen(),addslashes(),class_exists()等,可见__toString的利用点还是很多的。

__wakeup()函数

执行unserialize()时,先会调用__wakeup()

unserialize()会检查存在一个__wakeup()方法。如果存在,则先会调用__wakeup()方法。

这里有一个比较常见的漏洞,用于绕过__wakeup():

class xctf{
    public $flag = '111';
    public function __wakeup(){
        exit('bad requests');
    }
}
?code=

序列化后为O:4:"xctf":1:{s:4:"flag";s:3:"111";},意味对象名称长度为4,包含属性数1,属性名称类型为string,长度为4,名称为"flag",属性值长度为3,属性值为"111"。__wakeup()漏洞就是与整个属性个数值有关。

当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。为了避开__wakeup()函数的exit(),可以上传O:4:"xctf":2:{s:4:"flag";s:3:"111";}

__sleep()函数

执行serialize()时,先会调用__sleep()

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,则该方法会优先被调用,然后才执行序列化操作。

此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。

如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。

<?php

class Person
{
    public $sex;
    public $name;
    public $age;
    public function __construct($name="",  $age=25, $sex='男')
    {
        $this->name = $name;
        $this->age  = $age;
        $this->sex  = $sex;
    }

    public function __sleep() {
        echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
        $this->name = base64_encode($this->name);
        return array('name', 'age'); // 这里必须返回一个数值,里边的元素表示返回的属性名称
    }
}
$person = new Person('小明'); // 初始赋值
echo serialize($person);
//O:6:"Person":2:{s:4:"name";s:8:"5bCP5piO";s:3:"age";i:25;}

__call()与__callStatic()

当调用一个对象中的不能用的方法的时候就回执行这个函数。
public mixed __call ( string $name , array $arguments )

string $name 是要调用的方法名。 array $arguments 参数是一个枚举数组,包含着要传递给$name的参数。


__call()成对出现的是__callStatic()方法,在静态上下文中调用一个不可用的方法时会执行这个方法。

<?php
class MethodTest
{
    public function __call($name, $arguments)
    {
        // 注意:$name的值区分大小写
        echo "Calling object method '$name' " . implode(', ', $arguments) . "\n";
    }
    
    // PHP 5.3.0之后的版本
    public static function __callStatic($name, $arguments) 
    {
        echo "Calling static method '$name' " . implode(', ', $arguments) . "\n";
    }
}   

$obj = new MethodTest;
$obj -> runTest('in object context');
// PHP 5.3.0 版本以后
MethodTest::runTest('in static context');

//Calling object method ‘runTest’ in object context
//Calling static method ‘runTest’ in static context

__get()与__set()

__get()是访问不存在的成员变量时调用的;

__set()是设置不存在的成员变量时调用的;

<?php
class Test{
       public $c=0;
       public $arr=array();
       
       public function __set($x,$y){
            echo $x . "/n";
            echo $y . "/n";
            $this->arr[$x]=$y;
        }    
        public function __get($x){
            echo "The value of $x is".$this->arr[$x];

        }   
} 
$a = new Test;
$a->b = 1 ;//成员变量b不存在,所以会调用__set
$a->c  = 2;//成员变量c存在,所以无任何输出
$d=$a->b;//  成员变量b不存在,所以会调用__get     
?>
b
1
The value of b is 1

__isset()与__unset()

class Test
{
    private $abc='abc';
}
$test=new Test();
var_dump(isset($test->abc));
//False
虽然类里面有abc这个属性,但是它是私有属性,是不能被外部所访问的,所以返回布尔FALSE。如果想让外部能够发现私有属性,可以设置__isset()方法。
class Test
{
    private $abc='abc';
public function __isset($var)
{
    return isset($this->$var)?true:false;
}

}
$test=new Test();
var_dump(isset($test->abc));
//True


当外部的isset($test->abc) 检测不到abc时,它就会自动去执行类里面的__isset()这个方法,然后返回结果。

__unset($var):用来删除私有属性,只有一个参数:属性名。

class Test
{
    private $abc='abc';

    public function __unset($var)
    {
        echo "__unset:".$var;
        unset($this->$var);
    }
}
$test=new Test();
unset($test->abc);

外部的unset($test->abc)检测不到abc时,设置了__unset()方法后就会去执行类里面的__unset()这个方法,然后删除属性。

__invoke()方法

实例化对象本身是不能被调用,但是类中如果实现 __invoke() 方法,则把实例对象当作方法调用,会自动调用到 __invoke() 方法,参数顺序相同。
<?php

class asdf
{
public function __invoke($param1, $param2)
{
var_dump($param1,$param2);
}
}
$obj = new asdf();
$obj(123, 456);
var_dump(is_callable($obj));
?>

//int(123)
//int(456)
//bool(true)

 
posted @ 2020-04-30 20:41  B216三个👎👎  阅读(239)  评论(0)    收藏  举报