Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-25243 — POC estable para CVE-2026-25243 (doble liberación en Redis RESTORE -> ejecución remota de código) | Kitploit
Herramientas/GitHubGitHub/captain-woof/cve-2026-25243
Análisis de VulnerabilidadesExplotaciónPost-ExplotaciónPruebas de PenetraciónRed TeamingSeguridad de Bases de DatosExplotación de Binarios
GitHubcaptain-woof/cve-2026-25243

CVE-2026-25243

POC estable para CVE-2026-25243 (doble liberación en Redis RESTORE -> ejecución remota de código)

Ver Repositorio
11hace 1 mesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-25243 — double-free (doble liberación) de Redis RESTORE → ejecución remota de código

Verificado contra Rocky Linux 8.10, aarch64, Redis 8.6.2, jemalloc 5.3.0.

Referencia: https://www.zeroday.cloud/blog/redis-cve-2026-25243-deep-dive

TLDR; Exploit estable, funciona contra varias distribuciones de SO y arquitecturas.


Resumen ejecutivo

¿Qué es? Una vulnerabilidad de corrupción de memoria en Redis que permite a un atacante autenticado ejecutar comandos arbitrarios como el usuario de Redis. El ataque se da en el mundo real y requiere solo un único comando RESTORE — una operación normal de Redis, no exclusiva de administradores. Este exploit demuestra RCE completa en menos de un segundo.

¿Impacto? Cualquier cliente Redis autenticado puede desencadenarla, y el daño es total: ejecución de código arbitrario en el proceso de Redis (a menudo ejecutándose como root en contenedores). No hay forma de mitigarla sin parchear el propio Redis.

¿Cómo funciona de un vistazo? Redis tiene una función de serialización (RESTORE) que toma un blob de datos binarios y lo reconstruye como un objeto Redis. El código que valida el formato del blob y el código que lo deserializa no se ponen de acuerdo sobre cómo analizar ciertas secuencias — un bug que el atacante explota para corromper el heap. Una vez corrompido el heap, el atacante obtiene la capacidad de leer y escribir en cualquier dirección de memoria del proceso de Redis, y desde ahí secuestra el estado interno del servidor para ejecutar un comando de shell.

La técnica real del exploit: Esto no es un simple fallo. Es una cadena de explotación del heap: corromper → solapar → R/W arbitrario → fuga de información → encontrar la estructura del servidor → secuestrar punteros de función → RCE. El exploit ejecuta 9 etapas y requiere filtrar múltiples direcciones en tiempo de ejecución, analizar estructuras binarias y detectar el aliasing de memoria. Lo que hace que funcione entre arquitecturas (x86-64, aarch64, etc.) es que todas las direcciones se filtran del propio objetivo, no se asumen.


1. La vulnerabilidad — en detalle

CVE-2026-25243 es un par de bugs de double-free (doble liberación) alcanzables desde un único comando RESTORE autenticado. RESTORE key ttl <serialized-value> deserializa un blob RDB controlado por el atacante; ambos bugs viven en la brecha entre el validador que comprueba el blob y el conversor que lo materializa.

Bug 1 — conversión de zipmap heredado (CWE-415, la ruta que usa este exploit). El validador de zipmap (zipmapValidateIntegrity()) y el conversor (zipmapNext()) discrepan sobre una codificación de longitud redundante. La longitud pequeña 4 puede escribirse legalmente en la forma larga de cinco bytes FE 04 00 00 00. El validador consume un número de bytes, el conversor otro — una desincronización de análisis de 4 bytes. Por tanto, el conversor recorre una estructura diferente de la que se validó, lpSafeToAdd() falla después de que el campo ya se haya insertado en el diccionario, y la ruta de limpieza libera el campo dos veces: una mediante dictRelease() y otra mediante sdsfree().

Bug 2 — carga de PEL de consumidores de stream (CWE-415). En rdbLoadStreamConsumersGroup(), un PEL de consumidor que contiene un ID de entrada duplicado hace que el segundo raxTryInsert() falle, lo que llama a streamFreeNACK() sobre un streamNACK que todavía pertenece al PEL global del grupo. Se libera dos veces. (Se puede seleccionar con --vuln-type stream).

Cualquiera de los dos bugs entrega al atacante un fragmento de memoria que está simultáneamente libre y referenciado — el punto de partida clásico para un exploit de solapamiento de heap.

Impacto: un cliente Redis autenticado (sin derechos de administrador, RESTORE es un comando de datos normal) obtiene ejecución de código arbitrario como el usuario redis — root en la imagen de contenedor predeterminada.

2. Cómo funciona el exploit

Nueve etapas, cada una de las cuales convierte una primitiva más débil en una más fuerte:

Cómo activarlo

root@kitploit:~
python3 exploit.py --host 127.0.0.1 --port 6379 \
    --password mypassword --cmd 'id > /tmp/pwned123.txt'

Verificación:

root@kitploit:~
cat /tmp/pwned123.txt
# uid=0(root) gid=0(root) groups=0(root)

3. Registro de cambios

2026-08-06 — reestructuración de portabilidad, fiabilidad y velocidad

Punto de partida: el exploit era solo para x86-64 y moría en la etapa 3 en el objetivo aarch64. Estado final: RCE completa en aarch64 Rocky Linux 8.10 en menos de un segundo, con 116 comandos Redis.

a) Huella digital del objetivo en tiempo de ejecución (nueva, etapa 0). Ya no se asume nada sobre el objetivo. INFO server + INFO memory proporcionan la versión de Redis, la arquitectura de CPU (de la línea os:), la familia de distribución (inferida de gcc_version), el asignador y — lo más importante — tres anclas de validación: process_id, executable y el stat_starttime exacto (server_time_usec/1e6 - uptime_in_seconds). Las etapas posteriores comparan contra estos valores en lugar de adivinar.

b) Disposición de memoria independiente de la arquitectura. Las cuatro constantes x86-64 codificadas (BINARY_ADDR_MIN/MAX, HEAP_ADDR_MIN/MAX) se sustituyen por una tabla por arquitectura (ARCH_PROFILES) que cubre x86_64, aarch64 (VA de 39 y 48 bits), riscv64, ppc64le y s390x, con las colocaciones ET_EXEC y ET_DYN para cada una, además de un fallback genérico amplio para cualquier cosa no listada. Esta fue la razón real del fallo del exploit en este objetivo: el puntero filtrado 0x0000ffff8a5fdf32 es una dirección mmap aarch64 perfectamente válida que la comprobación de rango x86-64 rechazaba.

c) Validación de fugas basada en consenso (etapa 3). En lugar de confiar en una ventana de heap codificada, el escaneo ahora recopila todos los objetos 1337.NNNNNN estructuralmente válidos en la memview y requiere que al menos dos de ellos deriven la misma dirección base de memview (ptr - offset_of_value). En la práctica, 502 candidatos coinciden, lo que es una prueba que ninguna tabla de rangos puede ofrecer. El puntero confirmado calibra entonces la ventana de heap en tiempo de ejecución. La validación de formato también se movió antes de la prueba de control de escritura (costosa en viajes de ida y vuelta).

d) Límite del escaneo de la etapa 3 (corrección de bug). El escaneo llegaba a 10 MB codificados mientras que la memview es de 1 MB, por lo que leía más allá del final, obtenía una respuesta vacía y abortaba con AssertionError: Empty data from memview. Ahora está limitado por el STRLEN real de la memview, lee 256 KB por viaje de ida y vuelta en lugar de 64 KB, y el inútil bucle de reintento con sleep de 6×1s ha desaparecido.

e) Etapa 5 reescrita: guiada por ELF, sin fallos (la grande). La implementación anterior escaneaba hacia delante desde un puntero de imagen, probando direcciones y leyendo la longitud que un encabezado SDS basura afirmara. En este objetivo, caminaba directamente fuera del final del segmento de solo lectura hacia el hueco no mapeado en 0x715000 y mataba al servidor (SIGSEGV en getrangeCommand → memcpy). El escaneo ciego no puede hacerse seguro. El reemplazo es determinista:

  1. Encontrar la base de la imagen. Bajar página por página desde el puntero de imagen filtrado más bajo. La sonda es gratuita: los primeros cinco bytes de toda imagen ELF64 son 7f 45 4c 46 02, y sdslen() toma su byte de banderas de ptr[-1] — así que apuntar el objeto secuestrado a base+5 hace que e_ident[EI_CLASS]=0x02 sea el byte de banderas, es decir, SDS_TYPE_16, cuya longitud es el uint16 en base+0 = 0x457f (0x7f45 en big-endian). Un STRLEN de exactamente 17791 es la firma ELF. No se necesita una copia local del binario — el encabezado se lee de la propia memoria del objetivo.
  2. Analizar los encabezados de programa para obtener los límites exactos en tiempo de ejecución de cada segmento PT_LOAD (manejando el sesgo de carga ET_DYN para objetivos PIE). Cada lectura posterior se limita a un mapeo real, por lo que el fallo por hueco no mapeado ahora es estructuralmente imposible.

f) Etapa 4 reforzada. El validador Lua toma la lista completa de rangos por arquitectura (de modo que una imagen no PIE en 0x400000 y una imagen PIE en 0xaaaa… se reconocen ambas) y excluye la ventana de heap calibrada. Devuelve varios candidatos en lugar de uno, por lo que una mala elección cuesta un reintento en lugar de la ejecución completa.

g) Etapa 7 autoverificable. enable_debug_cmd se localizaba mediante un stat_starttime - 0x3c codificado. Ahora el valor esperado de stat_starttime se conoce exactamente desde INFO (una ventana de 3 segundos en lugar de 30 días), la ventana de lectura de la estructura creció de 4 KB a 32 KB (stat_starttime está en el offset 0x9e0, muy por detrás del límite anterior) y — de forma decisiva — cada offset candidato se verifica con un oráculo en vivo: establecer el byte, enviar DEBUG SET-ACTIVE-EXPIRE 1, y ver si el servidor lo acepta. Las conjeturas erróneas se restauran antes del siguiente intento, por lo que la bandera se encuentra en cualquier build en lugar de asumirse. -0x3c todavía se prueba primero y se confirma correcto para 8.6.2 (offset 0x9a4).

h) Las escrituras realmente llegan (etapa 5). setrangeCommand() llama a dbUnshareStringValue(), que duplica el valor a menos que encoding == RAW && refcount == 1. El byte de codificación ahora se pone a cero antes de la primera escritura a través del puntero secuestrado, por lo que las escrituras alcanzan la dirección objetivo en lugar de una copia privada.

i) Payload simplificado. Se eliminó todo el mecanismo de backconnect/reverse-shell, el banner ASCII y el ;sleep 5 añadido. El payload es exactamente /bin/sh -c '<--cmd>' y nada más. --cmd por defecto es id > /tmp/pwned123.txt.

j) Velocidad. La etapa 4 recopila 3 candidatos en lugar de 8; la etapa 5 sustituye ~10^5 sondas de bytes por ~40 lecturas masivas; la etapa 3 usa lecturas de 256 KB y omite los viajes de ida y vuelta para los candidatos que fallan la validación local. Cadena completa: 116 comandos, <1 s.

Resultado: uid=0(root) gid=0(root) groups=0(root) en /tmp/pwned123.txt en el contenedor objetivo.

Notas de portabilidad

  • La arquitectura se detecta, no se asume. La etapa 5 basada en ELF es neutral en cuanto a arquitectura por construcción (lee los propios encabezados de programa del objetivo) y maneja tanto imágenes PIE como no PIE, little- y big-endian.
  • La distribución se informa para beneficio del operador; el exploit no tiene una dependencia funcional de ella. La única suposición de sistema de archivos es /bin/sh, que requieren POSIX y el FHS.
  • Versión: la versión RDB y el tamaño de la estructura del stream se seleccionan de redis_version (se admiten 7.x y 8.x). Los offsets de campos de la estructura (executable=24, exec_argv=32) se derivan del ABI LP64, y enable_debug_cmd se descubre y verifica en tiempo de ejecución en lugar de codificarse.
  • Los objetivos de 32 bits se rechazan explícitamente en la etapa 0 (el payload construye punteros de 64 bits) en lugar de fallar de forma oscura más adelante.

2026-08-06 (más tarde) — endurecimiento de estabilidad tras pruebas de ejecución repetida

Se midieron 13/13 ejecuciones exitosas en la ruta zipmap predeterminada (5 + 8 consecutivas), completando cada una en ≤1 segundo. Tres problemas surgieron solo bajo ejecución repetida y ahora están corregidos:

k) Condición de carrera con SAVE en la etapa 0. Una ejecución podía abortar con ERR Background save already in progress cuando un guardado en segundo plano de una ejecución anterior (o del propio redis) seguía en curso. SAVE ahora se reintenta hasta 15 segundos y, si falla, la ejecución continúa sin el punto de control en lugar de abortar.

l) Reconexión mientras el objetivo se reinicia (--connect-retries, por defecto 10). Un intento fallido deja el heap corrompido, por lo que el FLUSHALL de la siguiente ejecución libera los fragmentos envenenados y derriba el servidor. Se reinicia segundos más tarde y es perfectamente explotable, por lo que la etapa 0 ahora se reconecta y reintenta en lugar de fallar. Nuestros propios errores de validación (versión/arquitectura no soportadas) nunca se reintentan. Esto eliminó el intermitente "stage 0 failed with an empty error" que se veía aproximadamente en 1 de cada 3 ejecuciones durante las pruebas de estrés.

m) sizeof(streamNACK) corregido para 8.6.x. La ruta --vuln-type stream rociaba la clase de tamaño jemalloc equivocada porque se asumía que la estructura era de 24 o 32 bytes. En 8.6.2 es de 64 bytes (delivery_time, delivery_count, consumer, cgroup_ref_node, streamID id, pel_prev, pel_next). Con el tamaño correcto, la ruta de stream ahora llega a la etapa 5 en lugar de fallar en la etapa 2 con "key overlap not found".

Limitaciones conocidas

  • --vuln-type stream no es fiable en 8.6.2. Con la corrección de tamaño, supera el double-free, el solapamiento, la primitiva R/W y el análisis ELF, y luego desestabiliza el espacio de claves: el servidor muere en setrangeCommand al leer o->ptr en NULL+8, es decir, una búsqueda de clave devuelve un objeto corrupto. El fragmento de 64 bytes que libera se comparte con otras asignaciones vivas, lo que lo hace mucho más propenso a daños colaterales que la ruta zipmap. Use el --vuln-type zipmap predeterminado, que es 13/13.
  • --random-heap-massage (100k claves aleatorias primero) tiene éxito pero no invariablemente — el heap rociado a veces coloca el fragmento doblemente liberado donde ninguna clave marcadora aterriza. Re-ejecutar tiene éxito.
  • Verificado solo en aarch64 / Rocky Linux 8.10 / Redis 8.6.2 (no PIE ET_EXEC). Las rutas x86-64 y PIE están implementadas y son neutrales en cuanto a arquitectura por construcción, pero no se han ejecutado contra un objetivo en vivo en esta sesión.
Descargar herramienta
EtapaPrimitiva obtenidaMecanismo
0perfil del objetivoINFO server / INFO memory → versión, arquitectura, distribución, pid, ruta del ejecutable, tiempo de inicio, asignador
1doble liberaciónRESTORE de zipmap (o stream) malformado
2dos claves que comparten memoriarociar claves marcadoras sobre el fragmento liberado, detectar aliasing, luego sobrescribir el encabezado SDS de una clave a través de su gemela para inflarlo a una "memview" de 1 MB
3R/W arbitrarioencontrar un objeto INCRBYFLOAT dentro de la memview, secuestrar su campo ptr: GETRANGE/SETRANGE sobre esa clave ahora lee/escribe cualquier dirección
4puntero de imagenescanear el heap hacia atrás en busca de un valor dentro de la imagen de redis-server
5&serverbajar hasta el encabezado ELF, analizar los encabezados de programa, volcar el segmento escribible, comparar server.pid
6payload en memoriaescribir "/bin/sh", "-c", "<cmd>" más un array argv en la memview
7estructura secuestradasobrescribir server.executable, server.exec_argv y server.enable_debug_cmd
8RCEDEBUG CRASH-AND-RECOVER → restartServer() → execve(server.executable, server.exec_argv, environ)
  • Forjar un encabezado SDS en una ranura puesta a cero del segmento escribible, lo que hace que todo .data/.bss sea legible en un puñado de viajes de ida y vuelta en lugar de cientos de miles de sondas de bytes. Los bytes sobrescritos se guardan y restauran.
  • Comparar server.pid con el pid de INFO — una prueba de igualdad exacta de 8 bytes — y luego confirmar desreferenciando server.executable y comparando la cadena con executable de INFO. El código antiguo aceptaba una heurística de forma suelta de siete campos; ahora la estructura se identifica positivamente.