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
24 changes: 21 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 @@ -97,6 +97,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 +1742,26 @@ 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()))
{
HttpConfiguration httpConfiguration = getHttpChannel().getHttpConfiguration();
if (httpConfiguration != null)
{
if (httpConfiguration.getHttpCompliance().allows(MISMATCHED_AUTHORITY))
{
if (getHttpChannel() instanceof ComplianceViolation.Listener)
((ComplianceViolation.Listener)getHttpChannel()).onComplianceViolation(httpConfiguration.getHttpCompliance(), 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 +1775,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 @@ -155,24 +155,24 @@ public static Stream<Arguments> cases()
new Request("HTTP/1.0 - No Host header, with X-Forwarded-Host")
.headers(
"GET /example HTTP/1.0",
"X-Forwarded-Host: alt.example.net:7070"
"X-Forwarded-Host: alt.example.org:7070"
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(7070)
.scheme("http").serverName("alt.example.org").serverPort(7070)
.secure(false)
.requestURL("http://alt.example.net:7070/example")
.requestURL("http://alt.example.org:7070/example")
),
Arguments.of(
new Request("HTTP/1.0 - Empty Host header, with X-Forwarded-Host")
.headers(
"GET /example HTTP/1.0",
"Host:",
"X-Forwarded-Host: alt.example.net:7070"
"X-Forwarded-Host: alt.example.org:7070"
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(7070)
.scheme("http").serverName("alt.example.org").serverPort(7070)
.secure(false)
.requestURL("http://alt.example.net:7070/example")
.requestURL("http://alt.example.org:7070/example")
),
// Host IPv4
Arguments.of(
Expand All @@ -199,7 +199,7 @@ public static Stream<Arguments> cases()
Arguments.of(new Request("IPv4 in Request Line")
.headers(
"GET https://1.2.3.4:2222/ HTTP/1.1",
"Host: wrong"
"Host: 1.2.3.4:2222"
gregw marked this conversation as resolved.
Show resolved Hide resolved
),
new Expectations()
.scheme("https").serverName("1.2.3.4").serverPort(2222)
Expand All @@ -209,7 +209,7 @@ public static Stream<Arguments> cases()
Arguments.of(new Request("IPv6 in Request Line")
.headers(
"GET http://[::1]:2222/ HTTP/1.1",
"Host: wrong"
"Host: [::1]:2222"
gregw marked this conversation as resolved.
Show resolved Hide resolved
),
new Expectations()
.scheme("http").serverName("[::1]").serverPort(2222)
Expand Down Expand Up @@ -749,25 +749,25 @@ public static Stream<Arguments> cases()
new Request("RFC7239 - mixed with HTTP/1.0 - No Host header")
.headers(
"GET /example HTTP/1.0",
"Forwarded: for=1.1.1.1:6060,proto=http;host=alt.example.net:7070"
"Forwarded: for=1.1.1.1:6060,proto=http;host=alt.example.org:7070"
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(7070)
.scheme("http").serverName("alt.example.org").serverPort(7070)
.secure(false)
.requestURL("http://alt.example.net:7070/example")
.requestURL("http://alt.example.org:7070/example")
.remoteAddr("1.1.1.1").remotePort(6060)
),
Arguments.of(
new Request("RFC7239 - mixed with HTTP/1.0 - Empty Host header")
.headers(
"GET /example HTTP/1.0",
"Host:",
"Forwarded: for=1.1.1.1:6060,proto=http;host=alt.example.net:7070"
"Forwarded: for=1.1.1.1:6060,proto=http;host=alt.example.org:7070"
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(7070)
.scheme("http").serverName("alt.example.org").serverPort(7070)
.secure(false)
.requestURL("http://alt.example.net:7070/example")
.requestURL("http://alt.example.org:7070/example")
.remoteAddr("1.1.1.1").remotePort(6060)
),
// =================================================================
Expand Down Expand Up @@ -890,15 +890,15 @@ public static Stream<Arguments> cases()
Arguments.of(new Request("https initial authority, X-Forwarded-Proto on http, Proxy-Ssl-Id exists (setSslIsSecure==false)")
.configureCustomizer((customizer) -> customizer.setSslIsSecure(false))
.headers(
"GET https://alt.example.net/foo HTTP/1.1",
"Host: myhost",
"GET https://alt.example.org/foo HTTP/1.1",
"Host: alt.example.org",
gregw marked this conversation as resolved.
Show resolved Hide resolved
"X-Forwarded-Proto: http",
"Proxy-Ssl-Id: Wibble"
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(80)
.scheme("http").serverName("alt.example.org").serverPort(80)
.secure(false)
.requestURL("http://alt.example.net/foo")
.requestURL("http://alt.example.org/foo")
.remoteAddr("0.0.0.0").remotePort(0)
.sslSession("Wibble")
),
Expand All @@ -920,63 +920,63 @@ public static Stream<Arguments> cases()
Arguments.of(new Request("Https initial authority, X-Proxied-Https off, Proxy-Ssl-Id exists (setSslIsSecure==true)")
.configureCustomizer((customizer) -> customizer.setSslIsSecure(true))
.headers(
"GET https://alt.example.net/foo HTTP/1.1",
"Host: myhost",
"GET https://alt.example.org/foo HTTP/1.1",
"Host: alt.example.org",
gregw marked this conversation as resolved.
Show resolved Hide resolved
"X-Proxied-Https: off", // this wins for scheme and secure
"Proxy-Ssl-Id: Wibble"
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(80)
.scheme("http").serverName("alt.example.org").serverPort(80)
.secure(false)
.requestURL("http://alt.example.net/foo")
.requestURL("http://alt.example.org/foo")
.remoteAddr("0.0.0.0").remotePort(0)
.sslSession("Wibble")
),
Arguments.of(new Request("Https initial authority, X-Proxied-Https off, Proxy-Ssl-Id exists (setSslIsSecure==true) (alt order)")
.configureCustomizer((customizer) -> customizer.setSslIsSecure(true))
.headers(
"GET https://alt.example.net/foo HTTP/1.1",
"Host: myhost",
"GET https://alt.example.org/foo HTTP/1.1",
"Host: alt.example.org",
gregw marked this conversation as resolved.
Show resolved Hide resolved
"Proxy-Ssl-Id: Wibble",
"X-Proxied-Https: off" // this wins for scheme and secure
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(80)
.scheme("http").serverName("alt.example.org").serverPort(80)
.secure(false)
.requestURL("http://alt.example.net/foo")
.requestURL("http://alt.example.org/foo")
.remoteAddr("0.0.0.0").remotePort(0)
.sslSession("Wibble")
),
Arguments.of(new Request("Http initial authority, X-Proxied-Https off, Proxy-Ssl-Id exists (setSslIsSecure==false)")
.configureCustomizer((customizer) -> customizer.setSslIsSecure(false))
.headers(
"GET https://alt.example.net/foo HTTP/1.1",
"Host: myhost",
"GET https://alt.example.org/foo HTTP/1.1",
"Host: alt.example.org",
gregw marked this conversation as resolved.
Show resolved Hide resolved
"X-Proxied-Https: off",
"Proxy-Ssl-Id: Wibble",
"Proxy-auth-cert: 0123456789abcdef"
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(80)
.scheme("http").serverName("alt.example.org").serverPort(80)
.secure(false)
.requestURL("http://alt.example.net/foo")
.requestURL("http://alt.example.org/foo")
.remoteAddr("0.0.0.0").remotePort(0)
.sslSession("Wibble")
.sslCertificate("0123456789abcdef")
),
Arguments.of(new Request("Http initial authority, X-Proxied-Https off, Proxy-Ssl-Id exists (setSslIsSecure==false) (alt)")
.configureCustomizer((customizer) -> customizer.setSslIsSecure(false))
.headers(
"GET https://alt.example.net/foo HTTP/1.1",
"Host: myhost",
"GET https://alt.example.org/foo HTTP/1.1",
"Host: alt.example.org",
gregw marked this conversation as resolved.
Show resolved Hide resolved
"Proxy-Ssl-Id: Wibble",
"Proxy-auth-cert: 0123456789abcdef",
"X-Proxied-Https: off"
),
new Expectations()
.scheme("http").serverName("alt.example.net").serverPort(80)
.scheme("http").serverName("alt.example.org").serverPort(80)
.secure(false)
.requestURL("http://alt.example.net/foo")
.requestURL("http://alt.example.org/foo")
.remoteAddr("0.0.0.0").remotePort(0)
.sslSession("Wibble")
.sslCertificate("0123456789abcdef")
Expand Down Expand Up @@ -1026,38 +1026,38 @@ public static Stream<Arguments> nonStandardPortCases()
Arguments.of(new Request("RFC7239 with https and h2")
.headers(
"GET /test/forwarded.jsp HTTP/1.1",
"Host: web.example.net",
"Forwarded: for=192.168.2.6;host=web.example.net;proto=https;proto-version=h2"
"Host: web.example.org",
"Forwarded: for=192.168.2.6;host=web.example.org;proto=https;proto-version=h2"
),
new Expectations()
.scheme("https").serverName("web.example.net").serverPort(443)
.requestURL("https://web.example.net/test/forwarded.jsp")
.scheme("https").serverName("web.example.org").serverPort(443)
.requestURL("https://web.example.org/test/forwarded.jsp")
.remoteAddr("192.168.2.6").remotePort(0)
),
// RFC7239 Tests with https and proxy provided port
Arguments.of(new Request("RFC7239 with proxy provided port on https and h2")
.headers(
"GET /test/forwarded.jsp HTTP/1.1",
"Host: web.example.net:9443",
"Forwarded: for=192.168.2.6;host=web.example.net:9443;proto=https;proto-version=h2"
"Host: web.example.org:9443",
"Forwarded: for=192.168.2.6;host=web.example.org:9443;proto=https;proto-version=h2"
),
new Expectations()
.scheme("https").serverName("web.example.net").serverPort(9443)
.requestURL("https://web.example.net:9443/test/forwarded.jsp")
.scheme("https").serverName("web.example.org").serverPort(9443)
.requestURL("https://web.example.org:9443/test/forwarded.jsp")
.remoteAddr("192.168.2.6").remotePort(0)
),
// RFC7239 Tests with https, no port in Host, but proxy provided port
Arguments.of(new Request("RFC7239 with client provided host and different proxy provided port on https and h2")
.headers(
"GET /test/forwarded.jsp HTTP/1.1",
"Host: web.example.net",
"Forwarded: for=192.168.2.6;host=new.example.net:7443;proto=https;proto-version=h2"
// Client: https://web.example.net/test/forwarded.jsp
// Proxy Requests: https://new.example.net/test/forwarded.jsp
"Host: web.example.org",
"Forwarded: for=192.168.2.6;host=new.example.org:7443;proto=https;proto-version=h2"
// Client: https://web.example.org/test/forwarded.jsp
// Proxy Requests: https://new.example.org/test/forwarded.jsp
),
new Expectations()
.scheme("https").serverName("new.example.net").serverPort(7443)
.requestURL("https://new.example.net:7443/test/forwarded.jsp")
.scheme("https").serverName("new.example.org").serverPort(7443)
.requestURL("https://new.example.org:7443/test/forwarded.jsp")
.remoteAddr("192.168.2.6").remotePort(0)
)
);
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