GHSA-7p3p-8qv8-m2vh Eclipse Jetty: HTTP Authority/Host mismatch
SummaryJetty 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 ImpactThis 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 controlvirtual host isolationmulti-tenant routing by hostnamelogin/logout/callback URL constructionreverse proxy and forwarded-header trust chainsauditing, cache keys, and absolute URL generationPotential consequences include:bypass of host-based ACLsvirtual host or tenant isolation failuresincorrect or attacker-influenced redirect/callback targetsinconsistent proxy/downstream interpretation of the original target hostmisleading logs and audit recordsTechnical Root CauseOn the HTTP/2 and HTTP/3 metadata builder paths:`:authority` is parsed separately into authority/URI state`Host` is preserved as a normal request headerthe two values are not compared for consistencyOn the HTTP/2 and HTTP/3 server entry paths:Jetty calls `ComplianceUtils.verify(httpCompliance, requestMetaData, listener)`this verification does not enforce `MISMATCHED_AUTHORITY`On the HTTP/1.1 path:Jetty explicitly checks whether authority and `Host` matchmismatches are rejected by defaultRelevant Code LocationsHTTP/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: MISMATCHED_AUTHORITYReproductionI reproduced this on local Jetty 12.1.9-SNAPSHOT source.Minimal reproduction steps:Start a Jetty HTTP/2 or HTTP/3 test server.Send a request with::authority = localhost:<port>Host = evil.example:<port>In the request handler, inspect both:Request.getServerName(request)request.getHeaders().get(HttpHeader.HOST)Observe whether Jetty rejects the request or allows both values to remain visible.
Observed result:HTTP/2: request is accepted and returns 200HTTP/3: request is accepted and returns 200the server can observe both:serverName=localhosthostHeader=evil.example:<port>This shows that a single attacker-controlled request can preserve two conflicting host interpretations inside Jetty.Tests UsedHTTP/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 400both exploitability tests pass, confirming that Jetty exposes different host values to different layersProject-Internal Evidence of Real ImpactExamples:`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 VersionConfirmed affected version:12.1.9-SNAPSHOTOther 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 FixRecommend 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 presentnormalize and compare themif they do not match, reject the request with 400 Bad Requestroute the failure through the existing MISMATCHED_AUTHORITY compliance mechanismAlso adding explicit HTTP/2 and HTTP/3 regression coverage for this case.Disclosure Statusnot publicly disclosedno public issue filedshared only privately with the Jetty security contacts
CVSS v3.1 score: 5.3