Repositorio completo de investigación sobre CVE-2026-42945 con análisis de desbordamiento de búfer en el heap, exploit de RCE (heap spray + Feng Shui), scripts de detección y guía de parcheo para la vulnerabilidad del módulo rewrite de NGINX.
Desbordamiento de búfer en el montón en NGINX ngx_http_rewrite_module
| Métrica | Valor |
|---|---|
| CVSS v4.0 | 9.2 (Crítico) |
| CVSS v3.1 | 8.1 (Alto) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | 122 — Desbordamiento de búfer basado en montón |
| Introducido | Junio 2008 — v0.6.27 |
| Descubierto | Abril 2026 — DepthFirst Research |
| Corregido | 13 de mayo de 2026 — v1.30.1, v1.31.0 |
| CVE Publicado | 21 de mayo de 2026 |
| Vida útil | ~18 años (no detectado) |
| Commit de corrección | 524977e7c534e87e5b55739fa74601c9f1102686 |
Un atacante remoto no autenticado puede desencadenar un desbordamiento de búfer determinista en el montón en los procesos worker de NGINX mediante el envío de una solicitud HTTP manipulada a un servidor con un patrón de configuración específico de rewrite + set/if/rewrite. El desbordamiento corrompe metadatos del montón (punteros ngx_pool_cleanup_t), lo que permite Ejecución Remota de Código (RCE) mediante técnicas de heap spray y Feng Shui.
server { listen 19321;
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1;
}
}
**Requisitos clave:**
- Una directiva `rewrite` cuya sustitución contenga `?` (separador de cadena de consulta)
- Una directiva `set`, `if` o `rewrite` posterior que haga referencia a una **captura PCRE sin nombre** (`$1`, `$2`, etc.)
- El `?` en la sustitución de rewrite activa `ngx_http_script_start_args_code` que establece `e->is_args = 1`
### Lo que un atacante puede lograr
| Capacidad | Descripción |
|-----------|-------------|
| **Denegación de Servicio** | Bloquear procesos worker de forma determinista, provocando bucles de reinicio (funciona independientemente de ASLR) |
| **Ejecución Remota de Código** | Con ASLR deshabilitado (o evitado mediante sobrescritura parcial), lograr RCE completa como usuario de nginx |
| **Exfiltración de Datos** | A través de primitivas de lectura de memoria, extraer datos sensibles del heap del worker |
| **Persistencia** | Instalar puertas traseras mediante ejecución de código en la memoria del proceso worker |
---
## 2. Análisis de Causa Raíz
### El Motor de Scripts de Dos Pasos
El `ngx_http_rewrite_module` de NGINX utiliza un **motor de scripts de dos pasos** en `src/http/ngx_http_script.c`:
1. **Pase de Longitud** (`ngx_http_script_run`): itera todos los códigos de script para calcular el tamaño total del búfer necesario. Escribe longitudes en `le.ip` y `le.pos`.
2. **Pase de Copia** (`ngx_http_script_copy_len`/`_code`): itera nuevamente, escribiendo bytes reales en el búfer preasignado en `e->ip` y `e->pos`.
Cada código de script tiene dos manejadores: uno para cada pase. Por ejemplo:
- `ngx_http_script_copy_len` → `ngx_http_script_copy_code`
- `ngx_http_script_start_args_len` → `ngx_http_script_start_args_code`
### La Bandera `is_args`
La bandera `e->is_args` en la **estructura del motor** (`ngx_http_script_engine_t`) controla cómo el pase de copia maneja ciertos caracteres:```c
typedef struct {
u_char *ip;
u_char *pos;
ngx_http_variable_value_t *sp;
ngx_str_t buf;
int flushed;
unsigned is_args:1; // <-- THE BUG
unsigned ncaptures:1;
ngx_uint_t captures_size;
// ...
} ngx_http_script_engine_t;
Cuando e->is_args = 1, el código de copia para las referencias de captura $N llama a ngx_escape_uri() con NGX_ESCAPE_ARGS, lo que expande:
+ → %2B (1 byte → 3 bytes, +200%)% → %25 (1 byte → 3 bytes, +200%)& → %26 (1 byte → 3 bytes, +200%)El flujo de ejecución para el patrón vulnerable:``` rewrite ^/api/(.*)$ /internal?migrated=true;
1. Durante la **evaluación de reescritura**, el motor encuentra `?` en la cadena de reemplazo, lo que activa `ngx_http_script_start_args_code`, estableciendo `e->is_args = 1`.
2. La reescritura modifica la URI de la solicitud y luego continúa a la siguiente directiva.
3. **`e->is_args` NUNCA SE LIMPIA**.
Entonces:```
set $original_endpoint $1;
le) para el pase de longitud: ```c
ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
Esto pone correctamente a cero le.is_args = 0, por lo que el pase de longitud devuelve la longitud de captura bruta, sin escapar.
e, que aún tiene e->is_args = 1 desde el paso 1. El pase de copia aplica el escape de URI, expandiendo cada carácter escapable de 1 byte a 3 bytes dentro de un búfer cuyo tamaño se basó en la longitud bruta — desbordamiento de montón.Pass 1 (Length — sub-engine le): le.is_args = 0 capture $1 = "A+++++B" → length = 7
Buffer allocated: 7 bytes
Pass 2 (Copy — main engine e): e.is_args = 1 ← LEAKED from rewrite capture $1 = "A+++++B" ngx_escape_uri("A+++++B", NGX_ESCAPE_ARGS): A → A (1 byte) + → %2B (3 bytes) ← EXPANSION + → %2B (3 bytes) + → %2B (3 bytes) + → %2B (3 bytes) + → %2B (3 bytes) B → B (1 byte) total written: 17 bytes buffer size: 7 bytes OVERFLOW: 10 bytes
La proporción de expansión es `7 + (n_escapable * 2)` donde `n_escapable` es el recuento de `+`, `%`, y `&` en la captura.
---
## 3. Mecánica de Explotación
### Resumen
| Paso | Técnica | Descripción |
|------|-----------|-------------|
| 1 | Desbordamiento | Enviar URI manipulada con relleno de `+` para desbordar el búfer del heap |
| 2 | Heap Spray | Publicar cuerpos grandes en `/spray` para llenar el heap con datos controlados |
| 3 | Feng Shui | Organizar asignaciones para que el objetivo del desbordamiento (`ngx_pool_cleanup_t`) sea adyacente |
| 4 | Corromper Handler | El desbordamiento sobrescribe `ngx_pool_cleanup_t.handler` con la dirección de `system()` |
| 5 | Activar Limpieza | Esperar la destrucción del pool → `system(cmd)` ejecuta el comando del atacante |
| 6 | Reverse Shell | Encadenar al payload de reverse shell para acceso interactivo |
### Feng Shui entre solicitudes
**El Feng Shui de una sola solicitud falla** porque el desbordamiento corrompe los metadatos del pool (`->d.next`, `->d.failed`) antes de llegar al puntero `cleanup`. Cuando el pool se destruye al final de la solicitud, los metadatos corruptos causan un **bloqueo antes de que se llame a `system()`**.