
Preuve de concept d'exploitabilité pour CVE-2026-43512 (Contournement de l'authentification Digest d'Apache Tomcat)
Résultat de l'analyse d'exploitabilité : la cause racine est confirmée. L'exploitation de bout en bout n'est pas reproductible sur un déploiement standard
UserDatabaseRealm. Voir Analyse pour les détails.
CVE-2026-43512 est une vulnérabilité dans le mécanisme d'authentification HTTP DIGEST d'Apache Tomcat. La méthode RealmBase.getDigest() ne valide pas la valeur de retour de getPassword(username) avant de construire l'entrée du hachage A1. Lorsqu'un nom d'utilisateur n'existe pas dans le Realm configuré, getPassword() retourne null, que l'opérateur de concaténation de chaînes de Java convertit silencieusement en la chaîne littérale "null".
Le serveur calcule donc :
A1 = MD5("<username>:<realm>:null")
Un client qui soumet une réponse DIGEST calculée avec la chaîne littérale "null" comme mot de passe produit un hachage identique. Selon l'avis de sécurité, cela constitue un contournement d'authentification.
Ce dépôt contient un environnement minimal reproductible et une preuve de concept en Go pour vérifier cette affirmation sur une instance réelle de Tomcat.
| Plage concernée | Corrigé dans |
|---|---|
| 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 |
Le chemin de code vulnérable dans RealmBase.java (toutes les branches concernées) :
// RealmBase.java — vulnérable
protected String getDigest(String username, String realmName, String algorithm) {
if (hasMessageDigest(algorithm)) {
return getPassword(username); // retourne null pour les utilisateurs inconnus
}
// null est concaténé comme la chaîne littérale "null" par Java
String a1 = username + ":" + realmName + ":" + getPassword(username);
return HexUtils.toHexString(
ConcurrentMessageDigest.digest(algorithm, a1.getBytes(...))
);
}
Le correctif (commit 6565a6c ajoute un contrôle explicite de null :
// RealmBase.java — corrigé
protected String getDigest(String username, String realmName, String algorithm) {
String password = getPassword(username);
if (password == null) {
return null;
}
...
}
L'exécution du PoC contre Tomcat 11.0.0-M1 avec la journalisation de niveau FINE révèle ce qui suit :
Digest: 2388e2c78407def640f37f092a8d3a84 ← client
Server digest: 2388e2c78407def640f37f092a8d3a84 ← serveur
Failed to authenticate user [ghost]
Les hachages digest correspondent. Le bogue dans getDigest() est réel et confirmé. Cependant, l'authentification échoue toujours car RealmBase.authenticate() dispose d'un second contrôle indépendant :
// RealmBase.authenticate()
if (serverDigest.equals(clientDigest)) {
return getPrincipal(username); // retourne null pour les utilisateurs inexistants
}
return null;
Dans un UserDatabaseRealm standard basé sur tomcat-users.xml, getPrincipal() effectue une recherche dans la base de données utilisateur en mémoire. Pour un nom d'utilisateur qui n'existe pas dans cette base, elle retourne null. L'appelant traite un Principal null comme un échec d'authentification et renvoie un 401.
cve-2026-43512-poc/
├── Dockerfile # Tomcat 11.0.0-M1 (version concernée)
├── tomcat-users.xml # Configuration minimale du Realm — aucun utilisateur "ghost"
├── web.xml
├── exploit/
│ ├── exploit.go # PoC — Go, stdlib uniquement
│ └── go.mod
└── README.md
| Outil | Version | Notes |
|---|---|---|
| Podman | ≥ 4.0 | Docker fonctionne aussi |
| Go | ≥ 1.22 | Uniquement pour exécuter l'exploit localement |
podman build -t tomcat-cve-2026-43512 .
podman run -d --name tomcat-vuln -p 8080:8080 tomcat-cve-2026-43512
Attendez quelques secondes que Tomcat termine son démarrage, puis vérifiez qu'il est actif :
curl -si http://localhost:8080/protected/secret.html | head -1
# Attendu : HTTP/1.1 401
cd exploit
go run exploit.go \
-target http://localhost:8080 \
-path /protected/secret.html \
-username ghost
Indicateurs disponibles :
| Indicateur | Valeur par défaut | Description |
|---|---|---|
-target | http://localhost:8080 | URL de base de Tomcat |
-path | /protected/ | Chemin de la ressource protégée |
-username | ghost | Nom d'utilisateur à utiliser — ne doit pas exister dans tomcat-users.xml |
Pour observer l'état interne de l'authentification, ajoutez un fichier logging.properties et montez-le :
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
Le journal affichera directement le résultat de la comparaison des digest, confirmant si les hachages correspondent.
podman stop tomcat-vuln && podman rm tomcat-vuln
============================================================
CVE-2026-43512 — Tomcat DIGEST Auth Bypass PoC
============================================================
Cible : http://localhost:8080/protected/secret.html
Utilisateur : "ghost" (ne doit PAS exister dans tomcat-users.xml)
Mot de passe : "null" (chaîne littérale)
------------------------------------------------------------
[1] Envoi d'une requête non authentifiée pour obtenir le défi DIGEST...
[+] HTTP 401 reçu — défi DIGEST :
Digest realm="UserDatabase", qop="auth", nonce="...", opaque="..."
[*] realm="UserDatabase" nonce="..." qop="auth" algorithm="MD5"
[2] Calcul de la réponse DIGEST avec password="null"...
Digest username="ghost", realm="UserDatabase", ...
[3] Envoi de la requête avec les identifiants DIGEST fabriqués...
------------------------------------------------------------
[✗] HTTP 401 — exploit échoué.
UserDatabaseRealm fournit une seconde ligne de défense :
getPrincipal("ghost") a retourné null après la correspondance du digest.
============================================================
| Ressource | Lien |
|---|---|
| Avis de sécurité Apache Tomcat | https://tomcat.apache.org/security-9.html |
| Commit de correction | 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 — Authentification HTTP Digest | https://datatracker.ietf.org/doc/html/rfc2617 |
| Analyse complète — article de blog | https://return-zero.dev/posts/cve-2026-43512 |