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:
parent
bd4cf3c835
commit
5f25f5b389
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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++));
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue