Fix/jetty 10.0.x/uri host mismatch alt (#9343)

* Introduce HttpCompliance.MISMATCHED_AUTHORITY

* Update HttpCompliance.RFC2616

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>

* Update NcsaRequestLogTest.testAbsolute

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>

* Use RFC2616 mode in RFC2616 tests

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>

* Alternative fix for mismatched host headers

This PR fixes the miss-matched host header issue in the Request.setMetaData method. This requires no change to the HttpParser.
A more comprehensive fix can be considered for jetty-12.

Signed-off-by: gregw <gregw@webtide.com>

* Alternative fix for mismatched host headers

Updates from review

Signed-off-by: gregw <gregw@webtide.com>

---------

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
Signed-off-by: gregw <gregw@webtide.com>
Co-authored-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
Greg Wilkins 2023-02-14 07:43:19 +11:00 committed by GitHub
parent bd4cf3c835
commit 5f25f5b389
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 69 additions and 10 deletions

View File

@ -117,7 +117,14 @@ public final class HttpCompliance implements ComplianceViolation.Mode
* should reject a request if the Host headers contains an invalid / unsafe authority. * should reject a request if the Host headers contains an invalid / unsafe authority.
* A deployment may include this violation to allow unsafe host headesr on a received request. * A deployment may include this violation to allow unsafe host headesr on a received request.
*/ */
UNSAFE_HOST_HEADER("https://www.rfc-editor.org/rfc/rfc7230#section-2.7.1", "Invalid Authority"); UNSAFE_HOST_HEADER("https://www.rfc-editor.org/rfc/rfc7230#section-2.7.1", "Invalid Authority"),
/**
* Since <a href="https://www.rfc-editor.org/rfc/rfc7230#section-5.4">RFC 7230: Section 5.4</a>, the HTTP protocol
* must reject a request if the target URI has an authority that is different than a provided Host header.
* A deployment may include this violation to allow different values on the target URI and the Host header on a received request.
*/
MISMATCHED_AUTHORITY("https://www.rfc-editor.org/rfc/rfc7230#section-5.4", "Mismatched Authority");
private final String url; private final String url;
private final String description; private final String description;
@ -162,7 +169,11 @@ public final class HttpCompliance implements ComplianceViolation.Mode
* The HttpCompliance mode that supports <a href="https://tools.ietf.org/html/rfc2616">RFC 7230</a> * The HttpCompliance mode that supports <a href="https://tools.ietf.org/html/rfc2616">RFC 7230</a>
* with only the violations that differ from {@link #RFC7230}. * with only the violations that differ from {@link #RFC7230}.
*/ */
public static final HttpCompliance RFC2616 = new HttpCompliance("RFC2616", of(Violation.HTTP_0_9, Violation.MULTILINE_FIELD_VALUE)); public static final HttpCompliance RFC2616 = new HttpCompliance("RFC2616", of(
Violation.HTTP_0_9,
Violation.MULTILINE_FIELD_VALUE,
Violation.MISMATCHED_AUTHORITY
));
/** /**
* A legacy HttpCompliance mode that allows all violations except case-insensitive methods. * A legacy HttpCompliance mode that allows all violations except case-insensitive methods.

View File

@ -42,7 +42,6 @@ import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;

View File

@ -66,6 +66,7 @@ import javax.servlet.http.WebConnection;
import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.ComplianceViolation; import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField; import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
@ -97,6 +98,8 @@ import org.eclipse.jetty.util.UrlEncoded;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static org.eclipse.jetty.http.HttpCompliance.Violation.MISMATCHED_AUTHORITY;
/** /**
* Jetty Request. * Jetty Request.
* <p> * <p>
@ -1740,9 +1743,28 @@ public class Request implements HttpServletRequest
throw new BadMessageException(badMessage); throw new BadMessageException(badMessage);
} }
HttpField host = getHttpFields().getField(HttpHeader.HOST);
if (uri.isAbsolute() && uri.hasAuthority() && uri.getPath() != null) if (uri.isAbsolute() && uri.hasAuthority() && uri.getPath() != null)
{ {
_uri = uri; _uri = uri;
if (host instanceof HostPortHttpField && !((HostPortHttpField)host).getHostPort().toString().equals(uri.getAuthority()))
{
HttpChannel httpChannel = getHttpChannel();
HttpConfiguration httpConfiguration = httpChannel.getHttpConfiguration();
if (httpConfiguration != null)
{
HttpCompliance httpCompliance = httpConfiguration.getHttpCompliance();
if (httpCompliance.allows(MISMATCHED_AUTHORITY))
{
if (httpChannel instanceof ComplianceViolation.Listener)
((ComplianceViolation.Listener)httpChannel).onComplianceViolation(httpCompliance, MISMATCHED_AUTHORITY, _uri.toString());
}
else
{
throw new BadMessageException(400, "Mismatched Authority");
}
}
}
} }
else else
{ {
@ -1756,10 +1778,9 @@ public class Request implements HttpServletRequest
if (!uri.hasAuthority()) if (!uri.hasAuthority())
{ {
HttpField field = getHttpFields().getField(HttpHeader.HOST); if (host instanceof HostPortHttpField)
if (field instanceof HostPortHttpField)
{ {
HostPortHttpField authority = (HostPortHttpField)field; HostPortHttpField authority = (HostPortHttpField)host;
builder.host(authority.getHost()).port(authority.getPort()); builder.host(authority.getHost()).port(authority.getPort());
} }

View File

@ -23,6 +23,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -38,7 +39,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class ForwardedRequestCustomizerTest public class ForwardedRequestCustomizerTest
{ {
private Server server; private Server server;
private RequestHandler handler;
private LocalConnector connector; private LocalConnector connector;
private LocalConnector connectorAlt; private LocalConnector connectorAlt;
private LocalConnector connectorConfigured; private LocalConnector connectorConfigured;
@ -68,6 +68,8 @@ public class ForwardedRequestCustomizerTest
// Default behavior Connector // Default behavior Connector
HttpConnectionFactory http = new HttpConnectionFactory(); HttpConnectionFactory http = new HttpConnectionFactory();
HttpCompliance mismatchedAuthorityHttpCompliance = HttpCompliance.RFC7230.with("Mismatched_Authority", HttpCompliance.Violation.MISMATCHED_AUTHORITY);
http.getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance);
http.getHttpConfiguration().setSecurePort(443); http.getHttpConfiguration().setSecurePort(443);
customizer = new ForwardedRequestCustomizer(); customizer = new ForwardedRequestCustomizer();
http.getHttpConfiguration().addCustomizer(customizer); http.getHttpConfiguration().addCustomizer(customizer);
@ -77,6 +79,7 @@ public class ForwardedRequestCustomizerTest
// Alternate behavior Connector // Alternate behavior Connector
HttpConnectionFactory httpAlt = new HttpConnectionFactory(); HttpConnectionFactory httpAlt = new HttpConnectionFactory();
httpAlt.getHttpConfiguration().setSecurePort(8443); httpAlt.getHttpConfiguration().setSecurePort(8443);
httpAlt.getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance);
customizerAlt = new ForwardedRequestCustomizer(); customizerAlt = new ForwardedRequestCustomizer();
httpAlt.getHttpConfiguration().addCustomizer(customizerAlt); httpAlt.getHttpConfiguration().addCustomizer(customizerAlt);
connectorAlt = new LocalConnector(server, httpAlt); connectorAlt = new LocalConnector(server, httpAlt);
@ -97,9 +100,10 @@ public class ForwardedRequestCustomizerTest
http.getHttpConfiguration().addCustomizer(customizerConfigured); http.getHttpConfiguration().addCustomizer(customizerConfigured);
connectorConfigured = new LocalConnector(server, http); connectorConfigured = new LocalConnector(server, http);
connectorConfigured.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance);
server.addConnector(connectorConfigured); server.addConnector(connectorConfigured);
handler = new RequestHandler(); RequestHandler handler = new RequestHandler();
server.setHandler(handler); server.setHandler(handler);
handler.requestTester = (request, response) -> handler.requestTester = (request, response) ->

View File

@ -17,6 +17,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpParser;
@ -47,6 +48,7 @@ public class PartialRFC2616Test
{ {
server = new Server(); server = new Server();
connector = new LocalConnector(server); connector = new LocalConnector(server);
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616);
connector.setIdleTimeout(10000); connector.setIdleTimeout(10000);
server.addConnector(connector); server.addConnector(connector);
@ -403,12 +405,19 @@ public class PartialRFC2616Test
} }
/**
* @throws Exception
* @see <a href="https://www.rfc-editor.org/rfc/rfc2616#section-5.2">RFC 2616 - Section 5.2 - The Resource Identified by a Request</a>
*/
@Test @Test
public void test521() throws Exception public void test521() throws Exception
{ {
// Default Host // Default Host
int offset = 0; int offset = 0;
String response = connector.getResponse("GET http://VirtualHost:8888/path/R1 HTTP/1.1\n" + "Host: wronghost\n" + "Connection: close\n" + "\n"); String response = connector.getResponse(
"GET http://VirtualHost:8888/path/R1 HTTP/1.1\n" +
"Host: wronghost\n" +
"Connection: close\n" + "\n");
offset = checkContains(response, offset, "HTTP/1.1 200", "Virtual host") + 1; offset = checkContains(response, offset, "HTTP/1.1 200", "Virtual host") + 1;
offset = checkContains(response, offset, "Virtual Dump", "Virtual host") + 1; offset = checkContains(response, offset, "Virtual Dump", "Virtual host") + 1;
offset = checkContains(response, offset, "pathInfo=/path/R1", "Virtual host") + 1; offset = checkContains(response, offset, "pathInfo=/path/R1", "Virtual host") + 1;

View File

@ -969,6 +969,15 @@ public class RequestTest
"Connection: close\n" + "Connection: close\n" +
"\n"); "\n");
i = 0; i = 0;
assertThat(response, containsString("400 Bad Request"));
results.clear();
response = _connector.getResponse(
"GET http://myhost:8888/ HTTP/1.1\n" +
"Host: myhost:8888\n" +
"Connection: close\n" +
"\n");
i = 0;
assertThat(response, containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/", results.get(i++)); assertEquals("http://myhost:8888/", results.get(i++));
assertEquals("0.0.0.0", results.get(i++)); assertEquals("0.0.0.0", results.get(i++));

View File

@ -190,7 +190,7 @@ public class NcsaRequestLogTest
_connector.getResponse( _connector.getResponse(
"GET http://hostname:8888/foo?name=value HTTP/1.1\n" + "GET http://hostname:8888/foo?name=value HTTP/1.1\n" +
"Host: servername\n" + "Host: hostname:8888\n" +
"\n"); "\n");
String log = _entries.poll(5, TimeUnit.SECONDS); String log = _entries.poll(5, TimeUnit.SECONDS);
assertThat(log, containsString("GET http://hostname:8888/foo?name=value")); assertThat(log, containsString("GET http://hostname:8888/foo?name=value"));

View File

@ -22,6 +22,12 @@
<Set name="sendDateHeader">false</Set> <Set name="sendDateHeader">false</Set>
<Set name="headerCacheSize">1024</Set> <Set name="headerCacheSize">1024</Set>
<Set name="httpCompliance">
<Call class="org.eclipse.jetty.http.HttpCompliance" name="from">
<Arg>RFC2616</Arg>
</Call>
</Set>
<!-- Uncomment to enable handling of X-Forwarded- style headers <!-- Uncomment to enable handling of X-Forwarded- style headers
<Call name="addCustomizer"> <Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg> <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>