Fast destruct

来看一个例子

<?php
class A{
    public $haha=1;
    public function __destruct()
    {
        echo 3;
    }
}
$a=new A;
$b=serialize($a);
$test=unserialize($b);
$test->haha=2;
echo $test->haha;

//233

可以发现一个很有意思的点,在unserialize和serialize两个函数结束后都没有马上执行echo 3;,而是在最后执行完echo $test->haha;才执行,原理如下

  1. 如果单独执行unserialize函数进行常规的反序列化,那么被反序列化后的整个对象的生命周期就仅限于这个函数执行的生命周期,当这个函数执行完毕,这个类就没了,在有析构函数的情况下就会执行它。
  2. 如果反序列化函数序列化出来的对象被赋给了程序中的变量,那么被反序列化的对象其生命周期就会变长,由于它一直都存在于这个变量当中,当这个对象被销毁,才会执行其析构函数

而有时候我们必须让destruct在unserialize结束后马上结束,就要用到fast destruct。操作起来很简单,一种方式就是修改序列化字符串的结构,使得完成部分反序列化的unserialize强制退出,提前触发__destruct,其中的几种方式如下

#修改序列化数字元素个数
a:2:{i:0;O:7:"myclass":1:{s:1:"a";O:5:"Hello":1:{s:3:"qwb";s:5:"/flag";}}}
#去掉序列化尾部 }
a:1:{i:0;O:7:"myclass":1:{s:1:"a";O:5:"Hello":1:{s:3:"qwb";s:5:"/flag";}}

本质上,fast destruct 是因为unserialize过程中扫描器发现序列化字符串格式有误导致的提前异常退出,为了销毁之前建立的对象内存空间,会立刻调用对象的__destruct(),提前触发反序列化链条。

serialize(unserialize($x)) != $x

先来看个正常的反序列过程

<?php
class A{
    public $a='b';
}
$raw='O:1:"A":1:{s:1:"a";s:1:"b";}';
var_dump(unserialize($raw));

//
object(A)#1 (1) {
  ["a"]=>
  string(1) "b"
}

理论上,serialize(unserialize($raw))应该就等于$raw本身,但存在一种特殊情况会导致不相等,在讨论前先来看一下下面这个例子

在php中有一个预置类叫__PHP_Incomplete_Class,用来存放“需要进行反序列化的不存在的对象”。什么意思呢,就像下面这样

<?php
$raw = 'O:1:"A":1:{s:1:"a";s:1:"b";}';
echo serialize(unserialize($raw));
//O:1:"A":1:{s:1:"a";s:1:"b";}

在这个脚本中并不存在A这个类,但还是反序列化再序列化成功了。来看看它怎么反序列化成功的

<?php
$raw='O:1:"A":1:{s:1:"a";s:1:"b";}';
var_dump(unserialize($raw));

//
object(__PHP_Incomplete_Class)#1 (2) {
  ["__PHP_Incomplete_Class_Name"]=>
  string(1) "A"
  ["a"]=>
  string(1) "b"
}

可以看到php把这个不存在的对象放到了__PHP_Incomplete_Class,类名为放到__PHP_Incomplete_Class_Name,剩下部分的放置和正常反序列化一样。那如果我们把__PHP_Incomplete_Class作为已知类进行反序列化呢

<?php
$raw='a:2:{i:0;O:8:"stdClass":1:{s:3:"abc";N;}i:1;O:22:"__PHP_Incomplete_Class":1:{s:3:"abc";s:3:"def";}}';
var_dump(unserialize($raw));
var_dump(serialize(unserialize($raw)));
var_dump(unserialize(serialize(unserialize($raw))));

//
array(2) {
  [0]=>
  object(stdClass)#1 (1) {
    ["abc"]=>
    NULL
  }
  [1]=>
  object(__PHP_Incomplete_Class)#2 (1) {
    ["abc"]=>
    string(3) "def"
  }
}
string(79) "a:2:{i:0;O:8:"stdClass":1:{s:3:"abc";N;}i:1;O:22:"__PHP_Incomplete_Class":0:{}}"
array(2) {
  [0]=>
  object(stdClass)#2 (1) {
    ["abc"]=>
    NULL
  }
  [1]=>
  object(__PHP_Incomplete_Class)#1 (0) {
  }
}

可以看到在第一次反序列化后__PHP_Incomplete_Class被当作普通类进行反序化,但进行序列化时会发现后面类变空了,也就是说在序列化时这个类被php识别为内置类的原样进行序列化,将我们自己写的内容覆盖掉。而因为__PHP_Incomplete_Class本来就是空的,所以我们序列化的结果就变成空的。也就实现了serialize(unserialize($x)) != $x

uniq

这个命令之前比较少用,主要用在去重。

如果想找到某个文件里每个字符不重复的一行,可以用uniq -d 1.txt

如果想找到某个文件里不重复的字符串,可以用uniq -u 1.txt