Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

URI Host Mismatch with optional Compliance modes #9343

Merged
merged 9 commits into from
Feb 13, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,14 @@ public enum Violation implements ComplianceViolation
* 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.
*/
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 description;
Expand Down Expand Up @@ -162,7 +169,11 @@ public String getDescription()
* 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}.
*/
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down
27 changes: 24 additions & 3 deletions jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField;
import org.eclipse.jetty.http.HttpField;
Expand Down Expand Up @@ -97,6 +98,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.eclipse.jetty.http.HttpCompliance.Violation.MISMATCHED_AUTHORITY;

/**
* Jetty Request.
* <p>
Expand Down Expand Up @@ -1740,9 +1743,28 @@ public void setMetaData(MetaData.Request request)
throw new BadMessageException(badMessage);
}

HttpField host = getHttpFields().getField(HttpHeader.HOST);
if (uri.isAbsolute() && uri.hasAuthority() && uri.getPath() != null)
{
_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());
}
gregw marked this conversation as resolved.
Show resolved Hide resolved
else
{
throw new BadMessageException(400, "Mismatched Authority");
}
}
}
}
else
{
Expand All @@ -1756,10 +1778,9 @@ public void setMetaData(MetaData.Request request)

if (!uri.hasAuthority())
{
HttpField field = getHttpFields().getField(HttpHeader.HOST);
if (field instanceof HostPortHttpField)
if (host instanceof HostPortHttpField)
{
HostPortHttpField authority = (HostPortHttpField)field;
HostPortHttpField authority = (HostPortHttpField)host;

builder.host(authority.getHost()).port(authority.getPort());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.jupiter.api.AfterEach;
Expand All @@ -38,7 +39,6 @@
public class ForwardedRequestCustomizerTest
{
private Server server;
private RequestHandler handler;
private LocalConnector connector;
private LocalConnector connectorAlt;
private LocalConnector connectorConfigured;
Expand Down Expand Up @@ -68,6 +68,8 @@ public void init() throws Exception

// Default behavior Connector
HttpConnectionFactory http = new HttpConnectionFactory();
HttpCompliance mismatchedAuthorityHttpCompliance = HttpCompliance.RFC7230.with("Mismatched_Authority", HttpCompliance.Violation.MISMATCHED_AUTHORITY);
http.getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance);
http.getHttpConfiguration().setSecurePort(443);
customizer = new ForwardedRequestCustomizer();
http.getHttpConfiguration().addCustomizer(customizer);
Expand All @@ -77,6 +79,7 @@ public void init() throws Exception
// Alternate behavior Connector
HttpConnectionFactory httpAlt = new HttpConnectionFactory();
httpAlt.getHttpConfiguration().setSecurePort(8443);
httpAlt.getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance);
customizerAlt = new ForwardedRequestCustomizer();
httpAlt.getHttpConfiguration().addCustomizer(customizerAlt);
connectorAlt = new LocalConnector(server, httpAlt);
Expand All @@ -97,9 +100,10 @@ public void init() throws Exception

http.getHttpConfiguration().addCustomizer(customizerConfigured);
connectorConfigured = new LocalConnector(server, http);
connectorConfigured.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(mismatchedAuthorityHttpCompliance);
server.addConnector(connectorConfigured);

handler = new RequestHandler();
RequestHandler handler = new RequestHandler();
server.setHandler(handler);

handler.requestTester = (request, response) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpParser;
Expand Down Expand Up @@ -47,6 +48,7 @@ public void init() throws Exception
{
server = new Server();
connector = new LocalConnector(server);
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616);
connector.setIdleTimeout(10000);
server.addConnector(connector);

Expand Down Expand Up @@ -403,12 +405,19 @@ public void test444() throws Exception

}

/**
* @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
public void test521() throws Exception
{
// Default Host
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, "Virtual Dump", "Virtual host") + 1;
offset = checkContains(response, offset, "pathInfo=/path/R1", "Virtual host") + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,15 @@ public void testHostPort() throws Exception
"Connection: close\n" +
"\n");
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"));
assertEquals("http://myhost:8888/", results.get(i++));
assertEquals("0.0.0.0", results.get(i++));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public void testAbsolute(String logType) throws Exception

_connector.getResponse(
"GET http://hostname:8888/foo?name=value HTTP/1.1\n" +
"Host: servername\n" +
"Host: hostname:8888\n" +
"\n");
String log = _entries.poll(5, TimeUnit.SECONDS);
assertThat(log, containsString("GET http://hostname:8888/foo?name=value"));
Expand Down
6 changes: 6 additions & 0 deletions tests/test-integration/src/test/resources/RFC2616Base.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
<Set name="sendDateHeader">false</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
<Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
Expand Down