ZendFramework的一些反序列化链
字数 2068 2025-08-07 08:22:05

ZendFramework 反序列化漏洞分析与利用

环境搭建

测试版本:

  • ZendFramework 1.12.20
  • PHP 5.6

环境搭建步骤:

  1. 下载ZendFramework: https://framework.zend.com/downloads/archives
  2. 创建项目: zf.bat create project zendApplication
  3. 在php.ini中添加include_path指向Zend目录
  4. 创建入口点文件: zendApplication/application/controllers/IndexController.php
<?php
class IndexController extends Zend_Controller_Action {
    public function init() {
        unserialize(base64_decode($_POST['a']));
    }
    public function indexAction() {
        // action body
    }
}

漏洞链分析

1. 文件删除漏洞链 (FD1)

调用链:
Zend_Http_Response_Stream::__destruct()

细节分析:

  • 入口点在library/Zend/Http/Response/Stream.php__destruct方法
  • 所有参数可控,可直接删除文件
public function __destruct() {
    if (is_resource($this->stream)) {
        fclose($this->stream);
        $this->stream = null;
    }
    if ($this->_cleanup) {
        @unlink($this->stream_name);  // 文件删除点
    }
}

利用条件:

  • 控制_cleanup为true
  • 控制stream_name为要删除的文件路径

2. 命令执行漏洞链 (RCE1)

调用链:
Zend_Log::__destruct() -> Zend_Log_Writer_Mail::shutdown() -> Zend_Layout::render() -> Zend_Filter_PregReplace::filter()

细节分析:

  1. 入口点: Zend_Log::__destruct()
public function __destruct() {
    foreach ($this->_writers as $writer) {
        $writer->shutdown();
    }
}
  • _writers可控,可调用任意类的shutdown方法
  1. 进入Zend_Log_Writer_Mail::shutdown()
public function shutdown() {
    if (empty($this->_eventsToMail)) return;
    if ($this->_subjectPrependText !== null) {
        $this->_mail->setSubject("...");
    }
    $this->_mail->setBodyText(implode('', $this->_eventsToMail));
    
    if ($this->_layout) {
        $this->_layout->events = implode('', $this->_layoutEventsToMail);
        $this->_mail->setBodyHtml($this->_layout->render());
    }
    $this->_mail->send();
}
  • 需要设置:
    • _eventsToMail = "aa" (绕过空判断)
    • _subjectPrependText = "null" (绕过null判断)
    • _mail = new Zend_Mail()
    • _layout = new Zend_Layout()
  1. 进入Zend_Layout::render()
public function render($name = null) {
    if ($this->inflectorEnabled() && (null !== ($inflector = $this->getInflector()))) {
        $name = $this->_inflector->filter(array('script' => $name));
    }
    // ...
}
  • 设置_inflectorEnabled = true
  • _inflector不为空
  1. 最终进入Zend_Filter_PregReplace::filter()
public function filter($value) {
    return preg_replace($this->_matchPattern, $this->_replacement, $value);
}
  • 控制_matchPattern_replacement实现命令执行
  • 示例: _matchPattern = "/.*/e", _replacement = "system('id');"

3. 命令执行漏洞链 (RCE2)

调用链:
Zend_Http_Response_Stream::__destruct() -> Zend_Form_Element::__toString() -> Zend_Form_Element::render() -> Zend_Form_Decorator_Form::render() -> Zend_Cache_Frontend_Function::call()

细节分析:

  1. 入口点: Zend_Http_Response_Stream::__destruct()
public function __destruct() {
    if ($this->_cleanup) {
        @unlink($this->stream_name);  // 触发__toString
    }
}
  • 设置_cleanup = true
  • stream_name为Zend_Form_Element对象
  1. 触发Zend_Form_Element::__toString()
public function __toString() {
    return $this->render();
}
  1. 进入Zend_Form_Element::render()
public function render(Zend_View_Interface $view = null) {
    foreach ($this->getDecorators() as $decorator) {
        $content = $decorator->render($content);
    }
    return $content;
}
  • 控制_decorators数组
  1. 进入Zend_Form_Decorator_Form::render()
public function render($content) {
    $form = $this->getElement();
    $view = $form->getView();
    $helper = $this->getHelper();
    $attribs = $this->getOptions();
    $name = $form->getFullyQualifiedName();
    return $view->$helper($name, $attribs, $content);
}
  • 关键点: $view->$helper($name, $attribs, $content)
  • 所有参数可控,可调用任意方法
  1. 最终进入Zend_Cache_Frontend_Function::call()
public function call($callback, array $parameters = array(), $tags = array(), $specificLifetime = false, $priority = 8) {
    if (!$cache) {
        return call_user_func_array($callback, $parameters);
    }
    // ...
}
  • 控制$callback$parameters实现命令执行
  • 绕过缓存检查条件: (($cacheBool1 || $cacheBool2) && (!$cacheBool3))为false

关键点总结

  1. 文件删除漏洞:

    • 利用Zend_Http_Response_Stream__destruct方法
    • 完全可控,直接删除指定文件
  2. 命令执行漏洞(RCE1):

    • 通过preg_replace/e模式执行代码
    • 需要构造复杂的对象关系链
  3. 命令执行漏洞(RCE2):

    • 通过call_user_func_array执行任意函数
    • 需要触发__toString魔术方法
    • 构造更复杂的对象关系,但功能更强大

防御建议

  1. 升级到最新版本的ZendFramework
  2. 避免反序列化用户可控的数据
  3. 使用安全的反序列化方法,如JSON
  4. 实施输入验证和过滤

参考

  • PHPGGC工具中已包含这些漏洞的利用链
  • 实际利用时可参考PHPGGC中的具体Payload构造方法
ZendFramework 反序列化漏洞分析与利用 环境搭建 测试版本 : ZendFramework 1.12.20 PHP 5.6 环境搭建步骤 : 下载ZendFramework: https://framework.zend.com/downloads/archives 创建项目: zf.bat create project zendApplication 在php.ini中添加include_ path指向Zend目录 创建入口点文件: zendApplication/application/controllers/IndexController.php 漏洞链分析 1. 文件删除漏洞链 (FD1) 调用链 : Zend_Http_Response_Stream::__destruct() 细节分析 : 入口点在 library/Zend/Http/Response/Stream.php 的 __destruct 方法 所有参数可控,可直接删除文件 利用条件 : 控制 _cleanup 为true 控制 stream_name 为要删除的文件路径 2. 命令执行漏洞链 (RCE1) 调用链 : Zend_Log::__destruct() -> Zend_Log_Writer_Mail::shutdown() -> Zend_Layout::render() -> Zend_Filter_PregReplace::filter() 细节分析 : 入口点 : Zend_Log::__destruct() _writers 可控,可调用任意类的 shutdown 方法 进入Zend_ Log_ Writer_ Mail::shutdown() 需要设置: _eventsToMail = "aa" (绕过空判断) _subjectPrependText = "null" (绕过null判断) _mail = new Zend_Mail() _layout = new Zend_Layout() 进入Zend_ Layout::render() 设置 _inflectorEnabled = true _inflector 不为空 最终进入Zend_ Filter_ PregReplace::filter() 控制 _matchPattern 和 _replacement 实现命令执行 示例: _matchPattern = "/.*/e" , _replacement = "system('id');" 3. 命令执行漏洞链 (RCE2) 调用链 : Zend_Http_Response_Stream::__destruct() -> Zend_Form_Element::__toString() -> Zend_Form_Element::render() -> Zend_Form_Decorator_Form::render() -> Zend_Cache_Frontend_Function::call() 细节分析 : 入口点 : Zend_Http_Response_Stream::__destruct() 设置 _cleanup = true stream_name 为Zend_ Form_ Element对象 触发Zend_ Form_ Element::__ toString() 进入Zend_ Form_ Element::render() 控制 _decorators 数组 进入Zend_ Form_ Decorator_ Form::render() 关键点: $view->$helper($name, $attribs, $content) 所有参数可控,可调用任意方法 最终进入Zend_ Cache_ Frontend_ Function::call() 控制 $callback 和 $parameters 实现命令执行 绕过缓存检查条件: (($cacheBool1 || $cacheBool2) && (!$cacheBool3)) 为false 关键点总结 文件删除漏洞 : 利用 Zend_Http_Response_Stream 的 __destruct 方法 完全可控,直接删除指定文件 命令执行漏洞(RCE1) : 通过 preg_replace 的 /e 模式执行代码 需要构造复杂的对象关系链 命令执行漏洞(RCE2) : 通过 call_user_func_array 执行任意函数 需要触发 __toString 魔术方法 构造更复杂的对象关系,但功能更强大 防御建议 升级到最新版本的ZendFramework 避免反序列化用户可控的数据 使用安全的反序列化方法,如JSON 实施输入验证和过滤 参考 PHPGGC工具中已包含这些漏洞的利用链 实际利用时可参考PHPGGC中的具体Payload构造方法