Skip to content

NMS-20060: Fixed CVE-2026-10050 - #4

Open
christianpape wants to merge 1 commit into
jetty-9.4.xfrom
jira/NMS-20060
Open

NMS-20060: Fixed CVE-2026-10050#4
christianpape wants to merge 1 commit into
jetty-9.4.xfrom
jira/NMS-20060

Conversation

@christianpape

Copy link
Copy Markdown

GHSA-2fvj-hgj9-j2gr / CVE-2026-10050: the Digest auth code hashed H(A1), H(A2) and the response with ISO-8859-1. String.getBytes(ISO_8859_1) silently maps every character above U+00FF to ? (0x3F), so a password like 密码 hashes to exactly the same bytes as the literal password ??. An attacker who knows the username of a user with a non-Latin-1 password can authenticate with a colliding ? password — and legitimate non-Latin-1 users can't authenticate reliably at all.

Upstream's fix (d0bb829, shipped in 12.0.36/12.1.10 and 9.4.63) is a full RFC 7616 refresh. I backported the security-relevant core of it rather than the whole rewrite, since the refresh also changes the default algorithm to SHA-256, requires 8-digit nc, replaces the nonce store, etc. — breaking changes not needed to close the CVE.

Changes

Server — jetty-security/.../DigestAuthenticator.java

  • Hash all digest parameters with UTF-8 (lossless for any string, so no substitution and no collision).
  • Advertise charset=UTF-8 in WWW-Authenticate, per RFC 7616 §3.3, so clients know what to hash with.
  • Accept the RFC 5987-encoded username* parameter (decoded before both H(A1) and the login-service lookup) so non-ASCII usernames can be carried; reject requests sending both username and username*.
  • tokenizer.setSingle(false): QuotedStringTokenizer was treating ' as a quote char and eating the '' delimiters out of UTF-8''%E7%94%A8.... Only " is special in an HTTP field value, so this is also just more correct.

Client — jetty-client/.../util/DigestAuthentication.java

  • Honour the server's charset parameter; fall back to ISO-8859-1 only for RFC 2617 servers that don't send one.
  • Replaced getBytes(...) with a strict encoder (CodingErrorAction.REPORT) that throws IllegalArgumentException instead of silently substituting ?. Against a legacy server a non-Latin-1 password now fails loudly rather than sending a colliding digest.
  • Send non-quoted-string-safe usernames as username*=UTF-8''… when the server advertises UTF-8.

jetty-jaspi/.../DigestAuthModule.java — this deprecated module duplicates the server logic verbatim and had the identical bypass. Same fix applied. It's outside the advisory's stated scope but it's shipped and exploitable.

Tests

ConstraintTest (jetty-security) — testDigestNonLatin1PasswordCollisionIsRejected asserts the collision exists at the hash level (digest(ISO_8859_1, …, "??") == digest(ISO_8859_1, …, PASSWORD_UTF8)), then that the colliding credential gets 401, that a legacy ISO-8859-1 client gets 401, and that the real password hashed with UTF-8 gets 200. Plus testDigestNonLatin1UserName for username* round-trip, mutual exclusion, and non-UTF-8 rejection.

HttpClientAuthenticationTest (jetty-client) — non-Latin-1 password and non-Latin-1 username end-to-end, plus testDigestNonLatin1PasswordWithoutCharsetFailsRatherThanColliding which asserts no Authorization header is sent at all against a charset-less server.

I verified these are real regression tests, not just passing tests: with the ISO-8859-1 hashing restored, the server collision test authenticates with 200 OK, and the client test finds an Authorization header where it expects a failure.

Results: ConstraintTest 46/46, HttpClientAuthenticationTest 40/40, integration DigestPostTest 4/4, full jetty-client suite 614 run / 0 failures. Three pre-existing failures are unrelated and fail identically on the pristine tree — AliasedConstraintTest.testAccess (macOS case-insensitive filesystem) and HttpClientTest.testBindAddress (BindException); jetty-jaspi's JaspiTest errors on a Geronimo/JAXB NoClassDefFound before reaching any auth code.

Two things to be aware of

  1. Interop change. The server now hashes with UTF-8 unconditionally. For ASCII credentials nothing changes (UTF-8 ≡ ISO-8859-1). But a client that ignores the charset parameter and uses Latin-1 for a password containing U+0080–U+00FF (e.g. ü) will stop authenticating. This is upstream's choice too — accepting both encodings would reintroduce the bypass. Similarly, the client now throws for a username that can't go in a quoted-string when the server doesn't advertise UTF-8, where it previously emitted raw non-ASCII octets in the header.
  2. Related, out of scope: Credential.MD5 in jetty-util (MD5: entries in realm.properties) has the same lossy getBytes(ISO_8859_1) pattern, affecting Basic/Form auth too. Upstream left it unchanged in 12.0.36 — changing it would invalidate every stored MD5 hash in every deployment. I left it alone but it's worth a separate decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant