
PoC эксплуатируемости для CVE-2026-43512 (Обход аутентификации Digest в Apache Tomcat)
Результат анализа эксплуатируемости: первопричина подтверждена. Скользящая эксплуатация не воспроизводится на стандартном развертывании
UserDatabaseRealm. Подробности см. в Анализе.
CVE-2026-43512 — это уязвимость в механизме аутентификации HTTP DIGEST в Apache Tomcat. Метод RealmBase.getDigest() не проверяет возвращаемое значение getPassword(username) перед построением входных данных для хэша A1. Если имя пользователя не существует в настроенном Realm, возвращает , который оператор конкатенации строк Java молча преобразует в четырехсимвольный литерал .
getPassword()null"null"Поэтому сервер вычисляет:
A1 = MD5("<username>:<realm>:null")
Клиент, отправляющий ответ DIGEST, вычисленный с literal-строкой "null" в качестве пароля, генерирует идентичный хэш. Согласно уведомлению об уязвимости, это представляет собой обход аутентификации.
Этот репозиторий содержит минимальную воспроизводимую среду и концепт-доказательство на Go для проверки этого утверждения на реальном экземпляре Tomcat.
| Затронутый диапазон | Исправлено в |
|---|---|
| 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 |
Уязвимый путь кода в RealmBase.java (все затронутые ветки):
// 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(...))
);
}
Исправление (коммит 6565a6c https://github.com/apache/tomcat/commit/6565a6cb6499e56fe2f34457cec99f9d1c4f39e9) добавляет явную проверку на null:
// RealmBase.java — patched
protected String getDigest(String username, String realmName, String algorithm) {
String password = getPassword(username);
if (password == null) {
return null;
}
...
}
Запуск PoC против Tomcat 11.0.0-M1 с включенным логированием уровня FINE показывает следующее:
Digest: 2388e2c78407def640f37f092a8d3a84 ← client
Server digest: 2388e2c78407def640f37f092a8d3a84 ← server
Failed to authenticate user [ghost]
Хэши дайджестов совпадают. Ошибка в getDigest() реальна и подтверждена. Однако аутентификация все равно не удается, потому что RealmBase.authenticate() имеет вторую независимую проверку:
// RealmBase.authenticate()
if (serverDigest.equals(clientDigest)) {
return getPrincipal(username); // returns null for non-existent users
}
return null;
В стандартном UserDatabaseRealm на основе tomcat-users.xml, getPrincipal() выполняет поиск в оперативной базе пользователей. Для имени пользователя, отсутствующего в этой базе, возвращается null. Вызывающий код рассматривает null Principal как сбой аутентификации и выдает 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
| Инструмент | Версия | Примечания |
|---|---|---|
| Podman | ≥ 4.0 | Docker тоже подойдет |
| Go | ≥ 1.22 | Только для локального запуска эксплойта |
podman build -t tomcat-cve-2026-43512 .
podman run -d --name tomcat-vuln -p 8080:8080 tomcat-cve-2026-43512
Подождите несколько секунд, пока Tomcat завершит запуск, затем проверьте, что он работает:
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
Доступные флаги:
| Флаг | По умолчанию | Описание |
|---|---|---|
-target | http://localhost:8080 | Базовый URL Tomcat |
-path | /protected/ | Путь к защищенному ресурсу |
-username | ghost | Имя пользователя — не должно существовать в tomcat-users.xml |
Чтобы наблюдать внутреннее состояние аутентификации, добавьте файл logging.properties и смонтируйте его:
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
В логе будет показан результат сравнения дайджестов, подтверждая, совпадают ли хэши.
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.
============================================================
| Ресурс | Ссылка |
|---|---|
| Уведомление о безопасности Apache Tomcat | https://tomcat.apache.org/security-9.html |
| Исправляющий коммит | https://github.com/apache/tomcat/commit/6565a6cb6499e56fe2f34457cec99f9d1c4f39e9 |
RealmBase.java (основной) | https://github.com/apache/tomcat/blob/main/java/org/apache/catalina/realm/RealmBase.java |
| RFC 2617 — Аутентификация HTTP Digest | https://datatracker.ietf.org/doc/html/rfc2617 |
| Полный анализ — запись в блоге | https://return-zero.dev/posts/cve-2026-43512 |
Этот репозиторий предназначен только для образовательных целей и анализа локальной эксплуатируемости. Все тестирование проводилось в среде собственного контейнера. Не запускайте этот PoC против систем, которыми вы не владеете или на тестирование которых у вас нет явного письменного разрешения.