diff --git a/jetty-documentation/src/main/asciidoc/old_docs/contributing/security.adoc b/jetty-documentation/src/main/asciidoc/old_docs/contributing/security.adoc deleted file mode 100644 index b8eb4bef1cc..00000000000 --- a/jetty-documentation/src/main/asciidoc/old_docs/contributing/security.adoc +++ /dev/null @@ -1,32 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under -// the terms of the Eclipse Public License 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0 -// -// This Source Code may also be made available under the following -// Secondary Licenses when the conditions for such availability set -// forth in the Eclipse Public License, v. 2.0 are satisfied: -// the Apache License v2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -[[security-reporting]] -=== Reporting Security Issues - -There are a number of avenues for reporting security issues to the Jetty project available. -If the issue is directly related to Jetty itself then reporting to the Jetty developers is encouraged. -The most direct method is to mail _security@webtide.com_. -Since Webtide is comprised of the active committers of the Jetty project this is our preferred reporting method. -We are generally flexible in how we work with reporters of security issues but we reserve the right to act in the interests of the Jetty project in all circumstances. - -If the issue is related to Eclipse or its Jetty integration then we encourage you to reach out to _security@eclipse.org_. - -If the issue is related to integrations with Jetty we are happy to work with you to identify the proper entity and either of the approaches above is fine. - -We prefer that security issues are reported directly to Jetty developers as opposed through GitHub Issues since it has no facility to tag issues as _private_. diff --git a/jetty-documentation/src/main/asciidoc/old_docs/troubleshooting/security-reports.adoc b/jetty-documentation/src/main/asciidoc/old_docs/troubleshooting/security-reports.adoc index cba06c71ed2..c39d6c770ab 100644 --- a/jetty-documentation/src/main/asciidoc/old_docs/troubleshooting/security-reports.adoc +++ b/jetty-documentation/src/main/asciidoc/old_docs/troubleshooting/security-reports.adoc @@ -19,147 +19,21 @@ [[security-reports]] === Jetty Security Reports -The following sections provide information about Jetty security issues. +==== List of Security Reports -If you would like to report a security issue please follow these link:#security-reporting[instructions]. +A current list of Jetty security reports can be viewed on the link:https://www.eclipse.org/jetty/security-reports.htmlhttps://www.eclipse.org/jetty/security-reports.html[Project Home Page.] -.Resolved Issues -[width="99%",cols="11%,19%,14%,9%,14%,14%,19%",options="header",] -|======================================================================= -|yyyy/mm/dd |ID |Exploitable |Severity |Affects |Fixed Version |Comment +==== Reporting Security Issues -|2019/08/13 |CVE-2019-9518 |Med |Med |< = 9.4.20 |9.4.21 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9518[Some HTTP/2 implementations are vulnerable to a flood of empty frames, potentially leading to a denial of service.] +There are a number of avenues for reporting security issues to the Jetty project available. -|2019/08/13 |CVE-2019-9516 |Med |Med |< = 9.4.20 |9.4.21 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9516[Some HTTP/2 implementations are vulnerable to a header leak, potentially leading to a denial of service.] +If the issue is directly related to Jetty itself then reporting to the Jetty developers is encouraged. +The most direct method is to mail _security@webtide.com_. +Since Webtide is comprised of the active committers of the Jetty project this is our preferred reporting method. +We are generally flexible in how we work with reporters of security issues but we reserve the right to act in the interests of the Jetty project in all circumstances. -|2019/08/13 |CVE-2019-9515 |Med |Med |< = 9.4.20 |9.4.21 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9515[Some HTTP/2 implementations are vulnerable to a settings flood, potentially leading to a denial of service when an attacker sent a stream of SETTINGS frames to the peer.] +If the issue is related to Eclipse or its Jetty integration then we encourage you to reach out to _security@eclipse.org_. -|2019/08/13 |CVE-2019-9514 |Med |Med |< = 9.4.20 |9.4.21 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9514[Some HTTP/2 implementations are vulnerable to a reset flood, potentially leading to a denial of service.] +If the issue is related to integrations with Jetty we are happy to work with you to identify the proper entity and either of the approaches above is fine. -|2019/08/13 |CVE-2019-9512 |Low |Low |< = 9.4.20 |9.4.21 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9512[Some HTTP/2 implementations are vulnerable to ping floods which could lead to a denial of service.] - -|2019/08/13 |CVE-2019-9511 |Low |Low |< = 9.4.20 |9.4.21 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9511[Some HTTP/2 implementations are vulnerable to window size manipulation and stream prioritization manipulation which could lead to a denial of service.] - -|2019/04/11 |CVE-2019-10247 |Med |Med |< = 9.4.16 |9.2.28, 9.3.27, 9.4.17 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-10247[If no webapp was mounted to the root namespace and a 404 was encountered, an HTML page would be generated displaying the fully qualified base resource location for each context.] - -|2019/04/11 |CVE-2019-10246 |High |High |< = 9.4.16 |9.2.28, 9.3.27, 9.4.17 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-10246[Use of `DefaultServlet` or `ResourceHandler` with indexing was vulnerable to XSS behaviors to expose the directory listing on Windows operating systems.] - -|2019/04/11 |CVE-2019-10241 |High |High |< = 9.4.15 |9.2.27, 9.3.26, 9.4.16 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-10241[Use of `DefaultServlet` or `ResourceHandler` with indexing was vulnerable to XSS behaviors to expose the directory listing.] - -|2018/06/25 |CVE-2018-12538 |High |High |>= 9.4.0, < = 9.4.8 |9.4.9 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-12538[`HttpSessions` present specifically in the FileSystem’s storage could be hijacked/accessed by an unauthorized user.] - -|2018/06/25 |CVE-2018-12536 |High |See https://cwe.mitre.org/data/definitions/209.html[CWE-202] |< = 9.4.10 |9.2.25, 9.3.24, 9.4.11 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-12536[`InvalidPathException` Message reveals webapp system path.] - -|2018/06/25 |CVE-2017-7658 |See https://cwe.mitre.org/data/definitions/444.html[CWE-444] |See https://cwe.mitre.org/data/definitions/444.html[CWE-444] |< = 9.4.10 |9.2.25, 9.3.24, 9.4.11 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=2017-7658[Too Tolerant Parser, Double Content-Length + Transfer-Encoding + Whitespace.] - -|2018/06/25 |CVE-2017-7657 |See https://cwe.mitre.org/data/definitions/444.html[CWE-444] |See https://cwe.mitre.org/data/definitions/444.html[CWE-444] |< = 9.4.10 |9.2.25, 9.3.24, 9.4.11 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7657[HTTP/1.1 Request smuggling with carefully crafted body content (Does not apply to HTTP/1.0 or HTTP/2).] - -|2018/06/25 |CVE-2017-7656 |See https://cwe.mitre.org/data/definitions/444.html[CWE-444] |See https://cwe.mitre.org/data/definitions/444.html[CWE-444] |< = 9.4.10 |9.2.25, 9.3.24, 9.4.11 -|https://cve.mitre.org/cgi-bin/cvename.cgi?name=2017-7656[HTTP Request Smuggling when used with invalid request headers (for HTTP/0.9).] - -|2016/05/31 |CVE-2016-4800 |high |high |>= 9.3.0, < = 9.3.8 |9.3.9 -|http://www.ocert.org/advisories/ocert-2016-001.html[Alias vulnerability allowing access to protected resources within a webapp on Windows.] - -|2015/02/24 |http://blog.gdssecurity.com/labs/2015/2/25/jetleak-vulnerability-remote-leakage-of-shared-buffers-in-je.html[CVE-2015-2080] |high |high |>=9.2.3 <9.2.9 |9.2.9 -|JetLeak exposure of past buffers during HttpParser error - -|2013/11/27 |http://en.securitylab.ru/lab/PT-2013-65[PT-2013-65] |medium -|high |>=9.0.0 <9.0.5 |9.0.6 -https://bugs.eclipse.org/bugs/show_bug.cgi?id=418014[418014] |Alias checking disabled by NTFS errors on Windows. - -|2013/07/24 -|https://bugs.eclipse.org/bugs/show_bug.cgi?id=413684[413684] |low -|medium |>=7.6.9 <9.0.5 |7.6.13,8.1.13,9.0.5 -https://bugs.eclipse.org/bugs/show_bug.cgi?id=413684[413684] -|Constraints bypassed if Unix symlink alias checker used on Windows. - -|2011/12/29 -|http://www.ocert.org/advisories/ocert-2011-003.html[CERT2011-003] http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-4461[CVE-2011-4461] -|high |medium |All versions |7.6.0.RCO -https://bugs.eclipse.org/bugs/show_bug.cgi?id=367638[Jetty-367638] -|Added ContextHandler.setMaxFormKeys (intkeys) to limit the number of parameters (default 1000). - -|2009/11/05 -|http://www.kb.cert.org/vuls/id/120541[CERT2011-003] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3555[CERT2011-003] -|medium |high |JVM<1.6u19 |jetty-7.01.v20091125, jetty-6.1.22 |Work -around by turning off SSL renegotiation in Jetty. If using JVM > 1.6u19 -setAllowRenegotiate(true) may be called on connectors. - -|2009/06/18 |Jetty-1042 |low -|high |< = 6.1.18, < = 7.0.0.M4 |6.1.19, 7.0.0.Rc0 |Cookie leak between -requests sharing a connection. - -|2009/04/30 |http://www.kb.cert.org/vuls/id/402580[CERT402580] |medium -|high |< = 6.1.16, < = 7.0.0.M2 a| -5.1.15, 6.1.18, 7.0.0.M2 - -Jetty-1004 - - |View arbitrary disk content in some specific configurations. - -|2007/12/22 -|http://www.kb.cert.org/vuls/id/553235[CERT553235] http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-6672[CVE-2007-6672] -|high |medium |6.1.rrc0-6.1.6 a| -6.1.7 - -CERT553235 - - |Static content visible in WEB-INF and past security constraints. - -|2007/11/05 -|http://www.kb.cert.org/vuls/id/438616[CERT438616] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-5614[CVE-2007-5614] -|low |low |<6.1.6 |6.1.6rc1 (patch in CVS for jetty5) |Single quote in -cookie name. - -|2007/11/05 -|http://www.kb.cert.org/vuls/id/237888[CERT237888>] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-5613[CVE-2007-5613] -|low |low |<6.1.6 |6.1.6rc0 (patch in CVS for jetty5) |XSS in demo dup -servlet. - -|2007/11/03 |http://www.kb.cert.org/vuls/id/212984[CERT212984 ->] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-5615[CVE-2007-5615] -|medium |medium |<6.1.6 |6.1.6rc0 (patch in CVS for jetty5) |CRLF -Response splitting. - -|2006/11/22 -|http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-6969[CVE-2006-6969] -|low |high |<6.1.0, <6.0.2, <5.1.12, <4.2.27 |6.1.0pre3, 6.0.2, 5.1.12, -4.2.27 |Session ID predictability. - -|2006/06/01 -|http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-2759[CVE-2006-2759] -|medium |medium |<6.0.*, <6.0.0Beta17 |6.0.0Beta17 |JSP source -visibility. - -|2006/01/05 | |medium |medium |<5.1.10 |5.1.10 |Fixed //security -constraint bypass on Windows. - -|2005/11/18 -|http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-2758[CVE-2006-2758] -|medium |medium |<5.1.6 |5.1.6, 6.0.0Beta4 |JSP source visibility. - -|2004/02/04 |JSSE 1.0.3_01 |medium |medium |<4.2.7 |4.2.7 |Upgraded JSSE -to obtain downstream security fix. - -|2002/09/22 | |high |high |<4.1.0 |4.1.0 |Fixed CGI servlet remove -exploit. - -|2002/03/12 | |medium | |<3.1.7 |4.0.RC2, 3.1.7 |Fixed // security -constraint bypass. - -|2001/10/21 |medium | |high |<3.1.3 |3.1.3 |Fixed trailing null security -constraint bypass. -|======================================================================= +We prefer that security issues are reported directly to Jetty developers as opposed through GitHub Issues since it currently has *no* facility to tag issues as _private_. diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java index dae3883aaba..91d3e266893 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java @@ -481,65 +481,60 @@ public class ForwardedRequestCustomizer implements Customizer if (match) { - String proto = "http"; - HttpURI.Mutable builder = HttpURI.build(request.getHttpURI()); boolean httpUriChanged = false; // Is secure status configured from headers? if (forwarded.isSecure()) { - // set default to https - proto = config.getSecureScheme(); + request.setSecure(true); } // Set Scheme from configured protocol if (forwarded._proto != null) { - proto = forwarded._proto; - builder.scheme(proto); + builder.scheme(forwarded._proto); + httpUriChanged = true; + } + // Set scheme if header implies secure scheme is to be used (see #isSslIsSecure()) + else if (forwarded._secureScheme) + { + builder.scheme(config.getSecureScheme()); httpUriChanged = true; } - - // Set authority - String host = null; - int port = -1; // Use authority from headers, if configured. if (forwarded._authority != null) { - host = forwarded._authority._host; - port = forwarded._authority._port; + String host = forwarded._authority._host; + int port = forwarded._authority._port; + + // Fall back to request metadata if needed. + if (host == null) + { + host = builder.getHost(); + } + + if (port == MutableHostPort.UNSET) // is unset by headers + { + port = builder.getPort(); + } + + // Don't change port if port == IMPLIED. + + // Update authority if different from metadata + if (!host.equalsIgnoreCase(builder.getHost()) || + port != builder.getPort()) + { + request.setHttpFields(HttpFields.build(httpFields, new HostPortHttpField(host, port))); + builder.authority(host, port); + httpUriChanged = true; + } } - // Fall back to request metadata if needed. - if (host == null) + if (httpUriChanged) { - host = builder.getHost(); - } - if (port == MutableHostPort.UNSET) // is unset by headers - { - port = builder.getPort(); - } - // Don't change port if port == IMPLIED. - - // Update authority if different from metadata - if (!host.equalsIgnoreCase(builder.getHost()) || - port != builder.getPort()) - { - request.setHttpFields(HttpFields.build(httpFields, new HostPortHttpField(host, port))); - builder.authority(host, port); - httpUriChanged = true; - } - - // Set secure status - if (forwarded.isSecure() || - proto.equalsIgnoreCase(config.getSecureScheme()) || - port == getSecurePort(config)) - { - request.setSecure(true); - builder.scheme(proto); - httpUriChanged = true; + request.setHttpURI(builder); } // Set Remote Address @@ -548,11 +543,6 @@ public class ForwardedRequestCustomizer implements Customizer int forPort = forwarded._for._port > 0 ? forwarded._for._port : request.getRemotePort(); request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._for._host, forPort)); } - - if (httpUriChanged) - { - request.setHttpURI(builder); - } } } @@ -759,6 +749,7 @@ public class ForwardedRequestCustomizer implements Customizer String _proto; Source _protoSource = Source.UNSET; Boolean _secure; + boolean _secureScheme = false; public Forwarded(Request request, HttpConfiguration config) { @@ -802,40 +793,58 @@ public class ForwardedRequestCustomizer implements Customizer return _for; } - @SuppressWarnings("unused") + /** + * Called if header is Proxy-auth-cert + */ public void handleCipherSuite(HttpField field) { _request.setAttribute("javax.servlet.request.cipher_suite", field.getValue()); + + // Is ForwardingRequestCustomizer configured to trigger isSecure and scheme change on this header? if (isSslIsSecure()) { _secure = true; + // track desire for secure scheme, actual protocol will be resolved later. + _secureScheme = true; } } - @SuppressWarnings("unused") + /** + * Called if header is Proxy-Ssl-Id + */ public void handleSslSessionId(HttpField field) { _request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue()); + + // Is ForwardingRequestCustomizer configured to trigger isSecure and scheme change on this header? if (isSslIsSecure()) { _secure = true; + // track desire for secure scheme, actual protocol will be resolved later. + _secureScheme = true; } } - @SuppressWarnings("unused") + /** + * Called if header is X-Forwarded-Host + */ public void handleForwardedHost(HttpField field) { updateAuthority(getLeftMost(field.getValue()), Source.XFORWARDED_HOST); } - @SuppressWarnings("unused") + /** + * Called if header is X-Forwarded-For + */ public void handleForwardedFor(HttpField field) { HostPort hostField = new HostPort(getLeftMost(field.getValue())); getFor().setHostPort(hostField, Source.XFORWARDED_FOR); } - @SuppressWarnings("unused") + /** + * Called if header is X-Forwarded-Server + */ public void handleForwardedServer(HttpField field) { if (getProxyAsAuthority()) @@ -843,7 +852,9 @@ public class ForwardedRequestCustomizer implements Customizer updateAuthority(getLeftMost(field.getValue()), Source.XFORWARDED_SERVER); } - @SuppressWarnings("unused") + /** + * Called if header is X-Forwarded-Port + */ public void handleForwardedPort(HttpField field) { int port = HostPort.parsePort(getLeftMost(field.getValue())); @@ -851,13 +862,17 @@ public class ForwardedRequestCustomizer implements Customizer updatePort(port, Source.XFORWARDED_PORT); } - @SuppressWarnings("unused") + /** + * Called if header is X-Forwarded-Proto + */ public void handleProto(HttpField field) { updateProto(getLeftMost(field.getValue()), Source.XFORWARDED_PROTO); } - @SuppressWarnings("unused") + /** + * Called if header is X-Proxied-Https + */ public void handleHttps(HttpField field) { if ("on".equalsIgnoreCase(field.getValue()) || "true".equalsIgnoreCase(field.getValue())) @@ -866,9 +881,21 @@ public class ForwardedRequestCustomizer implements Customizer updateProto(HttpScheme.HTTPS.asString(), Source.XPROXIED_HTTPS); updatePort(getSecurePort(_config), Source.XPROXIED_HTTPS); } + else if ("off".equalsIgnoreCase(field.getValue()) || "false".equalsIgnoreCase(field.getValue())) + { + _secure = false; + updateProto(HttpScheme.HTTP.asString(), Source.XPROXIED_HTTPS); + updatePort(MutableHostPort.IMPLIED, Source.XPROXIED_HTTPS); + } + else + { + throw new BadMessageException("Invalid value for " + field.getName()); + } } - @SuppressWarnings("unused") + /** + * Called if header is Forwarded + */ public void handleRFC7239(HttpField field) { addValue(field.getValue()); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index cf8e24d5516..5414f93c6a6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -38,6 +38,7 @@ import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; @@ -693,6 +694,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor _request.setMetaData(request); + _request.setSecure(HttpScheme.HTTPS.is(request.getURI().getScheme())); + _combinedListener.onRequestBegin(_request); if (LOG.isDebugEnabled()) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java index 1d07a6baf6f..2c82a4f081c 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java @@ -32,7 +32,6 @@ import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -135,6 +134,51 @@ public class ForwardedRequestCustomizerTest public static Stream cases() { return Stream.of( + // HTTP 1.0 + Arguments.of( + new Request("HTTP/1.0 - no Host header") + .headers( + "GET /example HTTP/1.0" + ), + new Expectations() + .scheme("http").serverName("0.0.0.0").serverPort(80) + .secure(false) + .requestURL("http://0.0.0.0/example") + ), + Arguments.of( + new Request("HTTP/1.0 - Empty Host header") + .headers( + "GET /example HTTP/1.0", + "Host:" + ), + new Expectations() + .scheme("http").serverName("0.0.0.0").serverPort(80) + .secure(false) + .requestURL("http://0.0.0.0/example") + ), + Arguments.of( + 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" + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(7070) + .secure(false) + .requestURL("http://alt.example.net: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" + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(7070) + .secure(false) + .requestURL("http://alt.example.net:7070/example") + ), // Host IPv4 Arguments.of( new Request("IPv4 Host Only") @@ -144,6 +188,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("1.2.3.4").serverPort(2222) + .secure(false) .requestURL("http://1.2.3.4:2222/") ), Arguments.of(new Request("IPv6 Host Only") @@ -153,16 +198,18 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("[::1]").serverPort(2222) + .secure(false) .requestURL("http://[::1]:2222/") ), Arguments.of(new Request("IPv4 in Request Line") .headers( - "GET http://1.2.3.4:2222/ HTTP/1.1", + "GET https://1.2.3.4:2222/ HTTP/1.1", "Host: wrong" ), new Expectations() - .scheme("http").serverName("1.2.3.4").serverPort(2222) - .requestURL("http://1.2.3.4:2222/") + .scheme("https").serverName("1.2.3.4").serverPort(2222) + .secure(true) + .requestURL("https://1.2.3.4:2222/") ), Arguments.of(new Request("IPv6 in Request Line") .headers( @@ -171,6 +218,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("[::1]").serverPort(2222) + .secure(false) .requestURL("http://[::1]:2222/") ), @@ -187,6 +235,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("[2001:db8:cafe::17]").remotePort(4711) ), @@ -200,6 +249,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("192.0.2.43").remotePort(0) ), @@ -213,6 +263,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("192.0.2.43").remotePort(0) ), @@ -227,6 +278,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("192.0.2.43").remotePort(0) ), @@ -240,6 +292,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("192.0.2.43").remotePort(0) ), @@ -251,6 +304,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("192.0.2.43").remotePort(0) ), @@ -264,6 +318,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("example.com").serverPort(80) + .secure(false) .requestURL("http://example.com/") .remoteAddr("192.0.2.43").remotePort(0) ), @@ -277,6 +332,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("myhost").serverPort(443) + .secure(true) .requestURL("https://myhost/") ), @@ -291,6 +347,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("example.com").serverPort(80) + .secure(false) .remoteAddr("10.20.30.40") .requestURL("http://example.com/") ), @@ -305,6 +362,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("example.com").serverPort(81) + .secure(true) .remoteAddr("10.20.30.40") .requestURL("https://example.com:81/") ), @@ -317,6 +375,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("example.com").serverPort(443) + .secure(true) .requestURL("https://example.com/") ), Arguments.of(new Request("ProxyPass (IPv6 from [::1]:80 to localhost:8080)") @@ -328,6 +387,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("[::1]").serverPort(80) + .secure(false) .remoteAddr("10.20.30.40") .requestURL("http://[::1]/") ), @@ -340,6 +400,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("[::1]").serverPort(8888) + .secure(false) .remoteAddr("10.20.30.40") .requestURL("http://[::1]:8888/") ), @@ -354,6 +415,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("example.com").serverPort(443) + .secure(true) .remoteAddr("10.20.30.40") .requestURL("https://example.com/") ), @@ -367,6 +429,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("myhost").serverPort(443) + .secure(true) .requestURL("https://myhost/") ), Arguments.of(new Request("X-Forwarded-For (multiple headers)") @@ -378,6 +441,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("10.9.8.7").remotePort(0) ), @@ -389,6 +453,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("10.9.8.7").remotePort(1111) ), @@ -400,6 +465,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("[2001:db8:cafe::17]").remotePort(1111) ), @@ -412,6 +478,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(2222) + .secure(false) .requestURL("http://myhost:2222/") .remoteAddr("[1:2:3:4:5:6:7:8]").remotePort(0) ), @@ -426,6 +493,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(2222) + .secure(false) .requestURL("http://myhost:2222/") .remoteAddr("[1:2:3:4:5:6:7:8]").remotePort(0) ), @@ -438,6 +506,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(2222) + .secure(false) .requestURL("http://myhost:2222/") .remoteAddr("[1:2:3:4:5:6:7:8]").remotePort(0) ), @@ -450,6 +519,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(4444) + .secure(false) .requestURL("http://myhost:4444/") .remoteAddr("192.168.1.200").remotePort(0) ), @@ -463,6 +533,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("192.168.1.200").remotePort(4444) ), @@ -475,6 +546,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(4444) + .secure(false) .requestURL("http://myhost:4444/") .remoteAddr("192.168.1.200").remotePort(0) ), @@ -489,6 +561,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("www.example.com").serverPort(4333) + .secure(true) .requestURL("https://www.example.com:4333/") .remoteAddr("8.5.4.3").remotePort(2222) ), @@ -503,6 +576,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("www.example.com").serverPort(4333) + .secure(true) .requestURL("https://www.example.com:4333/") .remoteAddr("8.5.4.3").remotePort(2222) ), @@ -518,6 +592,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("www.example.com").serverPort(4333) + .secure(true) .requestURL("https://www.example.com:4333/") .remoteAddr("8.5.4.3").remotePort(2222) ), @@ -533,6 +608,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("www.example.com").serverPort(4333) + .secure(true) .requestURL("https://www.example.com:4333/") .remoteAddr("8.5.4.3").remotePort(2222) ), @@ -548,6 +624,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("www.example.com").serverPort(4333) + .secure(true) .requestURL("https://www.example.com:4333/") .remoteAddr("8.5.4.3").remotePort(2222) ), @@ -561,6 +638,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("fw.example.com").serverPort(4333) + .secure(false) .requestURL("http://fw.example.com:4333/") .remoteAddr("8.5.4.3").remotePort(2222) ), @@ -574,6 +652,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("fw.example.com").serverPort(4333) + .secure(false) .requestURL("http://fw.example.com:4333/") .remoteAddr("8.5.4.3").remotePort(2222) ), @@ -589,6 +668,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("sub1.example.com").serverPort(10003) + .secure(true) .requestURL("https://sub1.example.com:10003/") .remoteAddr("127.0.0.1").remotePort(8888) ), @@ -604,6 +684,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("sub1.example.com").serverPort(10003) + .secure(true) .requestURL("https://sub1.example.com:10003/") .remoteAddr("127.0.0.1").remotePort(8888) ), @@ -620,6 +701,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("sub1.example.com").serverPort(10003) + .secure(true) .requestURL("https://sub1.example.com:10003/") .remoteAddr("127.0.0.1").remotePort(8888) ), @@ -635,6 +717,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("sub1.example.com").serverPort(10003) + .secure(true) .requestURL("https://sub1.example.com:10003/") .remoteAddr("127.0.0.1").remotePort(8888) ), @@ -651,9 +734,35 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("example.com").serverPort(80) + .secure(false) .requestURL("http://example.com/") .remoteAddr("192.0.2.43").remotePort(0) ), + Arguments.of( + 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" + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(7070) + .secure(false) + .requestURL("http://alt.example.net: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" + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(7070) + .secure(false) + .requestURL("http://alt.example.net:7070/example") + .remoteAddr("1.1.1.1").remotePort(6060) + ), // ================================================================= // Forced Behavior Arguments.of(new Request("Forced Host (no port)") @@ -666,6 +775,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("always.example.com").serverPort(80) + .secure(false) .requestURL("http://always.example.com/") .remoteAddr("11.9.8.7").remotePort(1111) ), @@ -679,6 +789,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("always.example.com").serverPort(9090) + .secure(false) .requestURL("http://always.example.com:9090/") .remoteAddr("11.9.8.7").remotePort(1111) ), @@ -692,6 +803,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("myhost").serverPort(443) + .secure(true) .requestURL("https://myhost/") .remoteAddr("0.0.0.0").remotePort(0) ), @@ -704,6 +816,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("0.0.0.0").remotePort(0) .sslSession("Wibble") @@ -717,6 +830,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("myhost").serverPort(443) + .secure(true) .requestURL("https://myhost/") .remoteAddr("0.0.0.0").remotePort(0) .sslSession("0123456789abcdef") @@ -730,6 +844,7 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("http").serverName("myhost").serverPort(80) + .secure(false) .requestURL("http://myhost/") .remoteAddr("0.0.0.0").remotePort(0) .sslCertificate("Wibble") @@ -743,9 +858,121 @@ public class ForwardedRequestCustomizerTest ), new Expectations() .scheme("https").serverName("myhost").serverPort(443) + .secure(true) .requestURL("https://myhost/") .remoteAddr("0.0.0.0").remotePort(0) .sslCertificate("0123456789abcdef") + ), + // ================================================================= + // Complicated scenarios + Arguments.of(new Request("No initial authority, X-Forwarded-Proto on http, Proxy-Ssl-Id exists (setSslIsSecure==true)") + .configureCustomizer((customizer) -> customizer.setSslIsSecure(true)) + .headers( + "GET /foo HTTP/1.1", + "Host: myhost", + "X-Forwarded-Proto: http", + "Proxy-Ssl-Id: Wibble" + ), + new Expectations() + .scheme("http").serverName("myhost").serverPort(80) + .secure(true) + .requestURL("http://myhost/foo") + .remoteAddr("0.0.0.0").remotePort(0) + .sslSession("Wibble") + ), + 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", + "X-Forwarded-Proto: http", + "Proxy-Ssl-Id: Wibble" + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(80) + .secure(false) + .requestURL("http://alt.example.net/foo") + .remoteAddr("0.0.0.0").remotePort(0) + .sslSession("Wibble") + ), + Arguments.of(new Request("No initial authority, X-Proxied-Https off, Proxy-Ssl-Id exists (setSslIsSecure==true)") + .configureCustomizer((customizer) -> customizer.setSslIsSecure(true)) + .headers( + "GET /foo HTTP/1.1", + "Host: myhost", + "X-Proxied-Https: off", // this wins for scheme and secure + "Proxy-Ssl-Id: Wibble" + ), + new Expectations() + .scheme("http").serverName("myhost").serverPort(80) + .secure(false) + .requestURL("http://myhost/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)") + .configureCustomizer((customizer) -> customizer.setSslIsSecure(true)) + .headers( + "GET https://alt.example.net/foo HTTP/1.1", + "Host: myhost", + "X-Proxied-Https: off", // this wins for scheme and secure + "Proxy-Ssl-Id: Wibble" + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(80) + .secure(false) + .requestURL("http://alt.example.net/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", + "Proxy-Ssl-Id: Wibble", + "X-Proxied-Https: off" // this wins for scheme and secure + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(80) + .secure(false) + .requestURL("http://alt.example.net/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", + "X-Proxied-Https: off", + "Proxy-Ssl-Id: Wibble", + "Proxy-auth-cert: 0123456789abcdef" + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(80) + .secure(false) + .requestURL("http://alt.example.net/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", + "Proxy-Ssl-Id: Wibble", + "Proxy-auth-cert: 0123456789abcdef", + "X-Proxied-Https: off" + ), + new Expectations() + .scheme("http").serverName("alt.example.net").serverPort(80) + .secure(false) + .requestURL("http://alt.example.net/foo") + .remoteAddr("0.0.0.0").remotePort(0) + .sslSession("Wibble") + .sslCertificate("0123456789abcdef") ) ); } @@ -848,20 +1075,31 @@ public class ForwardedRequestCustomizerTest expectations.accept(actual); } - @Test - public void testBadInput() throws Exception + public static Stream badRequestCases() { - Request request = new Request("Bad port value") - .headers( - "GET / HTTP/1.1", - "Host: myhost", - "X-Forwarded-Port: " - ); + return Stream.of( + new Request("Bad port value") + .headers( + "GET / HTTP/1.1", + "Host: myhost", + "X-Forwarded-Port: " + ), + new Request("Invalid X-Proxied-Https value") + .headers( + "GET / HTTP/1.1", + "Host: myhost", + "X-Proxied-Https: foo" + ) + ); + } + @ParameterizedTest + @MethodSource("badRequestCases") + public void testBadInput(Request request) throws Exception + { request.configure(customizer); String rawRequest = request.getRawRequest((header) -> header); - // System.out.println(rawRequest); HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(rawRequest)); assertThat("status", response.getStatus(), is(400)); @@ -926,12 +1164,13 @@ public class ForwardedRequestCustomizerTest int expectedRemotePort = 0; String expectedSslSession; String expectedSslCertificate; + Boolean secure; @Override public void accept(Actual actual) { assertThat("scheme", actual.scheme.get(), is(expectedScheme)); - if (actual.scheme.get().equals("https")) + if (secure != null && secure) { assertTrue(actual.wasSecure.get(), "wasSecure"); } @@ -953,6 +1192,12 @@ public class ForwardedRequestCustomizerTest } } + public Expectations secure(boolean flag) + { + this.secure = flag; + return this; + } + public Expectations scheme(String scheme) { this.expectedScheme = scheme;