## https://sploitus.com/exploit?id=08061788-8EBE-50F9-A0E6-81E77A550A2D
# CVE-2026-43512 โ Apache Tomcat DIGEST Authentication Bypass
> **Exploitability analysis result:** the root cause is confirmed. End-to-end exploitation is **not reproducible** against a standard `UserDatabaseRealm` deployment. See [Analysis](#analysis) for details.
---
## Table of Contents
- [Overview](#overview)
- [Affected Versions](#affected-versions)
- [Root Cause](#root-cause)
- [Analysis](#analysis)
- [Repository Structure](#repository-structure)
- [Requirements](#requirements)
- [Usage](#usage)
- [Expected Output](#expected-output)
- [References](#references)
- [Disclaimer](#disclaimer)
---
## Overview
CVE-2026-43512 is a vulnerability in Apache Tomcat's HTTP DIGEST authentication mechanism. The method `RealmBase.getDigest()` does not validate the return value of `getPassword(username)` before constructing the A1 hash input. When a username does not exist in the configured Realm, `getPassword()` returns `null`, which Java's string concatenation operator silently converts to the four-character literal `"null"`.
The server therefore computes:
```
A1 = MD5("::null")
```
A client that submits a DIGEST response computed with the literal string `"null"` as the password produces an identical hash. According to the advisory, this constitutes an authentication bypass.
This repository contains a minimal reproducible environment and a Go-based proof of concept to verify that claim against a real Tomcat instance.
---
## Affected Versions
| Affected range | Fixed 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 |
---
## Root Cause
The vulnerable code path in `RealmBase.java` (all affected branches):
```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(...))
);
}
```
The fix ([commit `6565a6c`](https://github.com/apache/tomcat/commit/6565a6cb6499e56fe2f34457cec99f9d1c4f39e9) adds an explicit null guard:
```java
// RealmBase.java โ patched
protected String getDigest(String username, String realmName, String algorithm) {
String password = getPassword(username);
if (password == null) {
return null;
}
...
}
```
---
## Analysis
Running the PoC against Tomcat 11.0.0-M1 with `FINE`-level logging enabled reveals the following:
```
Digest: 2388e2c78407def640f37f092a8d3a84 โ client
Server digest: 2388e2c78407def640f37f092a8d3a84 โ server
Failed to authenticate user [ghost]
```
**The digest hashes match.** The bug in `getDigest()` is real and confirmed. However, authentication still fails because `RealmBase.authenticate()` has a second, independent check:
```java
// RealmBase.authenticate()
if (serverDigest.equals(clientDigest)) {
return getPrincipal(username); // returns null for non-existent users
}
return null;
```
In a standard `UserDatabaseRealm` backed by `tomcat-users.xml`, `getPrincipal()` performs a lookup against the in-memory user database. For a username that does not exist in that database, it returns `null`. The caller treats a `null` Principal as an authentication failure and issues a `401`.
---
## Repository Structure
```
cve-2026-43512-poc/
โโโ Dockerfile # Tomcat 11.0.0-M1 (affected version)
โโโ tomcat-users.xml # Minimal Realm config โ no user "ghost"
โโโ webapps/
โ โโโ protected/
โ โโโ WEB-INF/
โ โ โโโ web.xml # DIGEST auth on /protected/*
โ โโโ secret.html # Protected resource
โโโ exploit/
โ โโโ exploit.go # PoC โ Go, stdlib only
โ โโโ go.mod
โโโ README.md
```
---
## Requirements
| Tool | Version | Notes |
|------|---------|-------|
| Podman | โฅ 4.0 | Docker works too |
| Go | โฅ 1.22 | Only for running the exploit locally |
---
## Usage
### 1. Build and start the container
```bash
podman build -t tomcat-cve-2026-43512 .
podman run -d --name tomcat-vuln -p 8080:8080 tomcat-cve-2026-43512
```
Wait a few seconds for Tomcat to finish starting, then verify it is up:
```bash
curl -si http://localhost:8080/protected/secret.html | head -1
# Expected: HTTP/1.1 401
```
### 2. Run the exploit
```bash
cd exploit
go run exploit.go \
-target http://localhost:8080 \
-path /protected/secret.html \
-username ghost
```
Available flags:
| Flag | Default | Description |
|------|---------|-------------|
| `-target` | `http://localhost:8080` | Tomcat base URL |
| `-path` | `/protected/` | Path of the protected resource |
| `-username` | `ghost` | Username to use โ must **not** exist in `tomcat-users.xml` |
### 3. Enable verbose Tomcat logging (optional)
To observe the internal authentication state, add a `logging.properties` file and mount it:
```properties
org.apache.catalina.authenticator.level = FINE
org.apache.catalina.realm.level = FINE
```
```bash
podman run -d --name tomcat-vuln -p 8080:8080 \
-v ./logging.properties:/usr/local/tomcat/conf/logging.properties:ro \
tomcat-cve-2026-43512
```
The log will show the digest comparison result directly, confirming whether the hashes match.
### 4. Cleanup
```bash
podman stop tomcat-vuln && podman rm tomcat-vuln
```
---
## Expected Output
```
============================================================
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.
============================================================
```
---
## References
| Resource | Link |
|----------|------|
| Apache Tomcat Security Advisory | https://tomcat.apache.org/security-9.html |
| Fix commit | 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 โ HTTP Digest Authentication | https://datatracker.ietf.org/doc/html/rfc2617 |
| Full analysis โ blog post | https://return-zero.dev/posts/cve-2026-43512 |
---
## Disclaimer
This repository is intended for educational purposes and local exploitability analysis only. All testing was performed against a self-hosted container environment. Do not run this PoC against systems you do not own or have explicit written authorization to test.