Share
## 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.