
PoC de explotabilidad para CVE-2026-43512 (Omisión de autenticación digest en Apache Tomcat)
Resultado del análisis de explotabilidad: la causa raíz está confirmada. La explotación de extremo a extremo no es reproducible contra un despliegue estándar de
UserDatabaseRealm. Consulta Análisis para más detalles.
CVE-2026-43512 es una vulnerabilidad en el mecanismo de autenticación DIGEST HTTP de Apache Tomcat. El método RealmBase.getDigest() no valida el valor de retorno de getPassword(username) antes de construir la entrada del hash A1. Cuando un nombre de usuario no existe en el Realm configurado, devuelve , que el operador de concatenación de cadenas de Java convierte silenciosamente en la cadena literal de cuatro caracteres .
getPassword()null"null"Por lo tanto, el servidor calcula:
A1 = MD5("<username>:<realm>:null")
Un cliente que envía una respuesta DIGEST calculada con la cadena literal "null" como contraseña produce un hash idéntico. Según el aviso, esto constituye un bypass de autenticación.
Este repositorio contiene un entorno reproducible mínimo y una prueba de concepto basada en Go para verificar esa afirmación contra una instancia real de Tomcat.
| Rango afectado | Corregido en |
|---|---|
| 7.0.0 – 7.0.109 | 7.0.110 |
| 8.5.0 – 8.5.100 | 8.5.101 |
| 9.0.0.M1 – 9.0.117 | 9.0.118 |
| 10.1.0.M1 – 10.1.54 | 10.1.55 |
| 11.0.0.M1 – 11.0.21 | 11.0.22 |
La ruta de código vulnerable en RealmBase.java (todas las ramas afectadas):
// RealmBase.java — vulnerable
protected String getDigest(String username, String realmName, String algorithm) {
if (hasMessageDigest(algorithm)) {
return getPassword(username); // returns null for unknown users
}
// null is concatenated as the literal "null" by Java
String a1 = username + ":" + realmName + ":" + getPassword(username);
return HexUtils.toHexString(
ConcurrentMessageDigest.digest(algorithm, a1.getBytes(...))
);
}
El parche (commit 6565a6c añade una comprobación explícita de null:
// RealmBase.java — patched
protected String getDigest(String username, String realmName, String algorithm) {
String password = getPassword(username);
if (password == null) {
return null;
}
...
}
Al ejecutar la PoC contra Tomcat 11.0.0-M1 con el registro de nivel FINE habilitado se observa lo siguiente:
Digest: 2388e2c78407def640f37f092a8d3a84 ← client
Server digest: 2388e2c78407def640f37f092a8d3a84 ← server
Failed to authenticate user [ghost]
Los hashes de digest coinciden. El fallo en getDigest() es real y está confirmado. Sin embargo, la autenticación sigue fallando porque RealmBase.authenticate() tiene una segunda comprobación independiente:
// RealmBase.authenticate()
if (serverDigest.equals(clientDigest)) {
return getPrincipal(username); // returns null for non-existent users
}
return null;
En un UserDatabaseRealm estándar respaldado por tomcat-users.xml, getPrincipal() realiza una búsqueda en la base de datos de usuarios en memoria. Para un nombre de usuario que no existe en esa base de datos, devuelve null. El llamador trata un Principal null como un fallo de autenticación y devuelve un 401.
cve-2026-43512-poc/
├── Dockerfile # Tomcat 11.0.0-M1 (affected version)
├── tomcat-users.xml # Minimal Realm config — no user "ghost"
├── web.xml
├── exploit/
│ ├── exploit.go # PoC — Go, stdlib only
│ └── go.mod
└── README.md
| Herramienta | Versión | Notas |
|---|---|---|
| Podman | ≥ 4.0 | Docker también funciona |
| Go | ≥ 1.22 | Solo para ejecutar el exploit localmente |
podman build -t tomcat-cve-2026-43512 .
podman run -d --name tomcat-vuln -p 8080:8080 tomcat-cve-2026-43512
Espera unos segundos a que Tomcat termine de iniciarse y verifica que está en marcha:
curl -si http://localhost:8080/protected/secret.html | head -1
# Expected: HTTP/1.1 401
cd exploit
go run exploit.go \
-target http://localhost:8080 \
-path /protected/secret.html \
-username ghost
Opciones disponibles:
| Indicador | Valor por defecto | Descripción |
|---|---|---|
-target | http://localhost:8080 | URL base de Tomcat |
-path | /protected/ | Ruta del recurso protegido |
-username | ghost | Nombre de usuario a usar — no debe existir en tomcat-users.xml |
Para observar el estado interno de autenticación, añade un archivo logging.properties y móntalo:
org.apache.catalina.authenticator.level = FINE
org.apache.catalina.realm.level = FINE
podman run -d --name tomcat-vuln -p 8080:8080 \
-v ./logging.properties:/usr/local/tomcat/conf/logging.properties:ro \
tomcat-cve-2026-43512
El registro mostrará directamente el resultado de la comparación del digest, confirmando si los hashes coinciden.
podman stop tomcat-vuln && podman rm tomcat-vuln
============================================================
CVE-2026-43512 — Tomcat DIGEST Auth Bypass PoC
============================================================
Target : http://localhost:8080/protected/secret.html
Username : "ghost" (must NOT exist in tomcat-users.xml)
Password : "null" (literal string)
------------------------------------------------------------
[1] Sending unauthenticated request to obtain DIGEST challenge...
[+] HTTP 401 received — DIGEST challenge:
Digest realm="UserDatabase", qop="auth", nonce="...", opaque="..."
[*] realm="UserDatabase" nonce="..." qop="auth" algorithm="MD5"
[2] Computing DIGEST response with password="null"...
Digest username="ghost", realm="UserDatabase", ...
[3] Sending request with crafted DIGEST credentials...
------------------------------------------------------------
[✗] HTTP 401 — exploit failed.
The UserDatabaseRealm provides a second line of defence:
getPrincipal("ghost") returned null after the digest matched.
============================================================
| Recurso | Enlace |
|---|---|
| Aviso de seguridad de Apache Tomcat | https://tomcat.apache.org/security-9.html |
| Commit de la corrección | https://github.com/apache/tomcat/commit/6565a6cb6499e56fe2f34457cec99f9d1c4f39e9 |
RealmBase.java (main) | https://github.com/apache/tomcat/blob/main/java/org/apache/catalina/realm/RealmBase.java |
| RFC 2617 — Autenticación HTTP Digest | https://datatracker.ietf.org/doc/html/rfc2617 |
| Análisis completo — entrada de blog | https://return-zero.dev/posts/cve-2026-43512 |
Este repositorio tiene fines exclusivamente educativos y de análisis de explotabilidad local. Todas las pruebas se realizaron contra un entorno de contenedores autoalojado. No ejecutes esta PoC contra sistemas que no sean de tu propiedad o para los que no tengas autorización explícita por escrito para realizar pruebas.