Apache Tomcat default CORS filter setting lets remote users bypass security
restrictions. This fix prevents the remote users from bypassing restrictions.
Change-Id: I8424c359a2f923764c6d1502392725783b9d68d2
Reviewed-on: http://photon-jenkins.eng.vmware.com:8082/5297
Tested-by: gerrit-photon <photon-checkins@vmware.com>
Reviewed-by: Keerthana K <keerthanak@vmware.com>
Reviewed-by: Alexey Makhalov <amakhalov@vmware.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,253 @@ |
| 0 |
+--- a/java/org/apache/catalina/filters/CorsFilter.java 2018/05/16 14:54:09 1831727 |
|
| 1 |
+@@ -267,17 +267,14 @@ |
|
| 2 |
+ |
|
| 3 |
+ // Section 6.1.3 |
|
| 4 |
+ // Add a single Access-Control-Allow-Origin header. |
|
| 5 |
+- if (anyOriginAllowed && !supportsCredentials) {
|
|
| 6 |
+- // If resource doesn't support credentials and if any origin is |
|
| 7 |
+- // allowed |
|
| 8 |
+- // to make CORS request, return header with '*'. |
|
| 9 |
++ if (anyOriginAllowed) {
|
|
| 10 |
++ // If any origin is allowed, return header with '*'. |
|
| 11 |
+ response.addHeader( |
|
| 12 |
+ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, |
|
| 13 |
+ "*"); |
|
| 14 |
+ } else {
|
|
| 15 |
+- // If the resource supports credentials add a single |
|
| 16 |
+- // Access-Control-Allow-Origin header, with the value of the Origin |
|
| 17 |
+- // header as value. |
|
| 18 |
++ // Add a single Access-Control-Allow-Origin header, with the value |
|
| 19 |
++ // of the Origin header as value. |
|
| 20 |
+ response.addHeader( |
|
| 21 |
+ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, |
|
| 22 |
+ origin); |
|
| 23 |
+@@ -781,6 +778,10 @@ |
|
| 24 |
+ // For any value other then 'true' this will be false. |
|
| 25 |
+ this.supportsCredentials = Boolean.parseBoolean(supportsCredentials); |
|
| 26 |
+ |
|
| 27 |
++ if (this.supportsCredentials && this.anyOriginAllowed) {
|
|
| 28 |
++ throw new ServletException(sm.getString("corsFilter.invalidSupportsCredentials"));
|
|
| 29 |
++ } |
|
| 30 |
++ |
|
| 31 |
+ try {
|
|
| 32 |
+ if (!preflightMaxAge.isEmpty()) {
|
|
| 33 |
+ this.preflightMaxAge = Long.parseLong(preflightMaxAge); |
|
| 34 |
+@@ -1090,7 +1091,7 @@ |
|
| 35 |
+ /** |
|
| 36 |
+ * By default, all origins are allowed to make requests. |
|
| 37 |
+ */ |
|
| 38 |
+- public static final String DEFAULT_ALLOWED_ORIGINS = "*"; |
|
| 39 |
++ public static final String DEFAULT_ALLOWED_ORIGINS = ""; |
|
| 40 |
+ |
|
| 41 |
+ /** |
|
| 42 |
+ * By default, following methods are supported: GET, POST, HEAD and OPTIONS. |
|
| 43 |
+@@ -1106,7 +1107,7 @@ |
|
| 44 |
+ /** |
|
| 45 |
+ * By default, support credentials is turned on. |
|
| 46 |
+ */ |
|
| 47 |
+- public static final String DEFAULT_SUPPORTS_CREDENTIALS = "true"; |
|
| 48 |
++ public static final String DEFAULT_SUPPORTS_CREDENTIALS = "false"; |
|
| 49 |
+ |
|
| 50 |
+ /** |
|
| 51 |
+ * By default, following headers are supported: |
|
| 52 |
+ |
|
| 53 |
+--- a/java/org/apache/catalina/filters/LocalStrings.properties 2018/05/16 14:54:09 1831727 |
|
| 54 |
+@@ -14,6 +14,8 @@ |
|
| 55 |
+ # limitations under the License. |
|
| 56 |
+ |
|
| 57 |
+ addDefaultCharset.unsupportedCharset=Specified character set [{0}] is not supported
|
|
| 58 |
++ |
|
| 59 |
++corsFilter.invalidSupportsCredentials=It is not allowed to configure supportsCredentials=[true] when allowedOrigins=[*] |
|
| 60 |
+ corsFilter.invalidPreflightMaxAge=Unable to parse preflightMaxAge |
|
| 61 |
+ corsFilter.nullRequest=HttpServletRequest object is null |
|
| 62 |
+ corsFilter.nullRequestType=CORSRequestType object is null |
|
| 63 |
+ |
|
| 64 |
+--- a/test/org/apache/catalina/filters/TestCorsFilter.java 2018/05/16 14:54:09 1831727 |
|
| 65 |
+@@ -55,8 +55,7 @@ |
|
| 66 |
+ corsFilter.doFilter(request, response, filterChain); |
|
| 67 |
+ |
|
| 68 |
+ Assert.assertTrue(response.getHeader( |
|
| 69 |
+- CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals( |
|
| 70 |
+- "https://www.apache.org")); |
|
| 71 |
++ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
|
|
| 72 |
+ Assert.assertTrue(((Boolean) request.getAttribute( |
|
| 73 |
+ CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue()); |
|
| 74 |
+ Assert.assertTrue(request.getAttribute( |
|
| 75 |
+@@ -88,8 +87,7 @@ |
|
| 76 |
+ corsFilter.doFilter(request, response, filterChain); |
|
| 77 |
+ |
|
| 78 |
+ Assert.assertTrue(response.getHeader( |
|
| 79 |
+- CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals( |
|
| 80 |
+- "https://www.apache.org")); |
|
| 81 |
++ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
|
|
| 82 |
+ Assert.assertTrue(((Boolean) request.getAttribute( |
|
| 83 |
+ CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue()); |
|
| 84 |
+ Assert.assertTrue(request.getAttribute( |
|
| 85 |
+@@ -120,8 +118,7 @@ |
|
| 86 |
+ corsFilter.doFilter(request, response, filterChain); |
|
| 87 |
+ |
|
| 88 |
+ Assert.assertTrue(response.getHeader( |
|
| 89 |
+- CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals( |
|
| 90 |
+- "https://www.apache.org")); |
|
| 91 |
++ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
|
|
| 92 |
+ Assert.assertTrue(((Boolean) request.getAttribute( |
|
| 93 |
+ CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue()); |
|
| 94 |
+ Assert.assertTrue(request.getAttribute( |
|
| 95 |
+@@ -166,41 +163,15 @@ |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ /* |
|
| 99 |
+- * Tests the presence of the origin (and not '*') in the response, when |
|
| 100 |
+- * supports credentials is enabled alongwith any origin, '*'. |
|
| 101 |
++ * Tests the that supports credentials may not be enabled with any origin, |
|
| 102 |
++ * '*'. |
|
| 103 |
+ * |
|
| 104 |
+- * @throws IOException |
|
| 105 |
+ * @throws ServletException |
|
| 106 |
+ */ |
|
| 107 |
+- @Test |
|
| 108 |
+- public void testDoFilterSimpleAnyOriginAndSupportsCredentials() |
|
| 109 |
+- throws IOException, ServletException {
|
|
| 110 |
+- TesterHttpServletRequest request = new TesterHttpServletRequest(); |
|
| 111 |
+- request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, |
|
| 112 |
+- TesterFilterConfigs.HTTPS_WWW_APACHE_ORG); |
|
| 113 |
+- request.setMethod("GET");
|
|
| 114 |
+- TesterHttpServletResponse response = new TesterHttpServletResponse(); |
|
| 115 |
+- |
|
| 116 |
++ @Test(expected=ServletException.class) |
|
| 117 |
++ public void testDoFilterSimpleAnyOriginAndSupportsCredentials() throws ServletException {
|
|
| 118 |
+ CorsFilter corsFilter = new CorsFilter(); |
|
| 119 |
+- corsFilter.init(TesterFilterConfigs |
|
| 120 |
+- .getFilterConfigAnyOriginAndSupportsCredentials()); |
|
| 121 |
+- corsFilter.doFilter(request, response, filterChain); |
|
| 122 |
+- |
|
| 123 |
+- Assert.assertTrue(response.getHeader( |
|
| 124 |
+- CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals( |
|
| 125 |
+- TesterFilterConfigs.HTTPS_WWW_APACHE_ORG)); |
|
| 126 |
+- Assert.assertTrue(response.getHeader( |
|
| 127 |
+- CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS) |
|
| 128 |
+- .equals( |
|
| 129 |
+- "true")); |
|
| 130 |
+- Assert.assertTrue(((Boolean) request.getAttribute( |
|
| 131 |
+- CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue()); |
|
| 132 |
+- Assert.assertTrue(request.getAttribute( |
|
| 133 |
+- CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals( |
|
| 134 |
+- TesterFilterConfigs.HTTPS_WWW_APACHE_ORG)); |
|
| 135 |
+- Assert.assertTrue(request.getAttribute( |
|
| 136 |
+- CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals( |
|
| 137 |
+- CorsFilter.CORSRequestType.SIMPLE.name().toLowerCase(Locale.ENGLISH))); |
|
| 138 |
++ corsFilter.init(TesterFilterConfigs.getFilterConfigAnyOriginAndSupportsCredentials()); |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ /* |
|
| 142 |
+@@ -261,8 +232,7 @@ |
|
| 143 |
+ corsFilter.doFilter(request, response, filterChain); |
|
| 144 |
+ |
|
| 145 |
+ Assert.assertTrue(response.getHeader( |
|
| 146 |
+- CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals( |
|
| 147 |
+- "https://www.apache.org")); |
|
| 148 |
++ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
|
|
| 149 |
+ Assert.assertTrue(response.getHeader( |
|
| 150 |
+ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS) |
|
| 151 |
+ .equals(TesterFilterConfigs.EXPOSED_HEADERS)); |
|
| 152 |
+@@ -727,9 +697,8 @@ |
|
| 153 |
+ }); |
|
| 154 |
+ corsFilter.doFilter(request, response, filterChain); |
|
| 155 |
+ |
|
| 156 |
+- Assert.assertTrue(response.getHeader( |
|
| 157 |
+- CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals( |
|
| 158 |
+- "https://www.apache.org")); |
|
| 159 |
++ Assert.assertNull(response.getHeader( |
|
| 160 |
++ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)); |
|
| 161 |
+ Assert.assertTrue(((Boolean) request.getAttribute( |
|
| 162 |
+ CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue()); |
|
| 163 |
+ Assert.assertTrue(request.getAttribute( |
|
| 164 |
+@@ -1412,7 +1381,7 @@ |
|
| 165 |
+ Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0); |
|
| 166 |
+ Assert.assertTrue(corsFilter.isAnyOriginAllowed()); |
|
| 167 |
+ Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0); |
|
| 168 |
+- Assert.assertTrue(corsFilter.isSupportsCredentials()); |
|
| 169 |
++ Assert.assertFalse(corsFilter.isSupportsCredentials()); |
|
| 170 |
+ Assert.assertTrue(corsFilter.getPreflightMaxAge() == 1800); |
|
| 171 |
+ } |
|
| 172 |
+ |
|
| 173 |
+@@ -1448,9 +1417,9 @@ |
|
| 174 |
+ Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 6); |
|
| 175 |
+ Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 4); |
|
| 176 |
+ Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0); |
|
| 177 |
+- Assert.assertTrue(corsFilter.isAnyOriginAllowed()); |
|
| 178 |
++ Assert.assertFalse(corsFilter.isAnyOriginAllowed()); |
|
| 179 |
+ Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0); |
|
| 180 |
+- Assert.assertTrue(corsFilter.isSupportsCredentials()); |
|
| 181 |
++ Assert.assertFalse(corsFilter.isSupportsCredentials()); |
|
| 182 |
+ Assert.assertTrue(corsFilter.getPreflightMaxAge() == 1800); |
|
| 183 |
+ } |
|
| 184 |
+ |
|
| 185 |
+@@ -1554,8 +1523,7 @@ |
|
| 186 |
+ corsFilter.doFilter(request, response, filterChain); |
|
| 187 |
+ |
|
| 188 |
+ Assert.assertTrue(response.getHeader( |
|
| 189 |
+- CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals( |
|
| 190 |
+- "https://www.apache.org")); |
|
| 191 |
++ CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
|
|
| 192 |
+ Assert.assertNull(request |
|
| 193 |
+ .getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)); |
|
| 194 |
+ Assert.assertNull(request |
|
| 195 |
+ |
|
| 196 |
+--- a/test/org/apache/catalina/filters/TesterFilterConfigs.java 2018/05/16 14:54:09 1831727 |
|
| 197 |
+@@ -36,12 +36,13 @@ |
|
| 198 |
+ public static final TesterServletContext mockServletContext = |
|
| 199 |
+ new TesterServletContext(); |
|
| 200 |
+ |
|
| 201 |
++ // Default config for the test is to allow any origin |
|
| 202 |
+ public static FilterConfig getDefaultFilterConfig() {
|
|
| 203 |
+ final String allowedHttpHeaders = |
|
| 204 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_HEADERS; |
|
| 205 |
+ final String allowedHttpMethods = |
|
| 206 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_METHODS; |
|
| 207 |
+- final String allowedOrigins = CorsFilter.DEFAULT_ALLOWED_ORIGINS; |
|
| 208 |
++ final String allowedOrigins = ANY_ORIGIN; |
|
| 209 |
+ final String exposedHeaders = CorsFilter.DEFAULT_EXPOSED_HEADERS; |
|
| 210 |
+ final String supportCredentials = |
|
| 211 |
+ CorsFilter.DEFAULT_SUPPORTS_CREDENTIALS; |
|
| 212 |
+@@ -59,7 +60,7 @@ |
|
| 213 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_HEADERS; |
|
| 214 |
+ final String allowedHttpMethods = |
|
| 215 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",PUT"; |
|
| 216 |
+- final String allowedOrigins = CorsFilter.DEFAULT_ALLOWED_ORIGINS; |
|
| 217 |
++ final String allowedOrigins = ANY_ORIGIN; |
|
| 218 |
+ final String exposedHeaders = CorsFilter.DEFAULT_EXPOSED_HEADERS; |
|
| 219 |
+ final String supportCredentials = "true"; |
|
| 220 |
+ final String preflightMaxAge = |
|
| 221 |
+@@ -77,7 +78,7 @@ |
|
| 222 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_HEADERS; |
|
| 223 |
+ final String allowedHttpMethods = |
|
| 224 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",PUT"; |
|
| 225 |
+- final String allowedOrigins = CorsFilter.DEFAULT_ALLOWED_ORIGINS; |
|
| 226 |
++ final String allowedOrigins = ANY_ORIGIN; |
|
| 227 |
+ final String exposedHeaders = CorsFilter.DEFAULT_EXPOSED_HEADERS; |
|
| 228 |
+ final String supportCredentials = "false"; |
|
| 229 |
+ final String preflightMaxAge = |
|
| 230 |
+@@ -131,7 +132,7 @@ |
|
| 231 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_HEADERS; |
|
| 232 |
+ final String allowedHttpMethods = |
|
| 233 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_METHODS; |
|
| 234 |
+- final String allowedOrigins = CorsFilter.DEFAULT_ALLOWED_ORIGINS; |
|
| 235 |
++ final String allowedOrigins = ANY_ORIGIN; |
|
| 236 |
+ final String exposedHeaders = EXPOSED_HEADERS; |
|
| 237 |
+ final String supportCredentials = |
|
| 238 |
+ CorsFilter.DEFAULT_SUPPORTS_CREDENTIALS; |
|
| 239 |
+@@ -240,7 +241,7 @@ |
|
| 240 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_HEADERS; |
|
| 241 |
+ final String allowedHttpMethods = |
|
| 242 |
+ CorsFilter.DEFAULT_ALLOWED_HTTP_METHODS; |
|
| 243 |
+- final String allowedOrigins = CorsFilter.DEFAULT_ALLOWED_ORIGINS; |
|
| 244 |
++ final String allowedOrigins = ANY_ORIGIN; |
|
| 245 |
+ final String exposedHeaders = CorsFilter.DEFAULT_EXPOSED_HEADERS; |
|
| 246 |
+ final String supportCredentials = |
|
| 247 |
+ CorsFilter.DEFAULT_SUPPORTS_CREDENTIALS; |
|
| 248 |
+ |
| ... | ... |
@@ -1,7 +1,7 @@ |
| 1 | 1 |
Summary: Apache Tomcat |
| 2 | 2 |
Name: apache-tomcat |
| 3 | 3 |
Version: 8.5.31 |
| 4 |
-Release: 1%{?dist}
|
|
| 4 |
+Release: 2%{?dist}
|
|
| 5 | 5 |
License: Apache |
| 6 | 6 |
URL: http://tomcat.apache.org |
| 7 | 7 |
Group: Applications/System |
| ... | ... |
@@ -14,6 +14,7 @@ Source0: http://mirrors.koehn.com/apache/tomcat/tomcat-8/v%{version}/src/
|
| 14 | 14 |
Source1: base-for-%{name}-%{version}.tar.gz
|
| 15 | 15 |
%define sha1 base=9c954df61d7c72f6d5e7319d25351e178a84cab4 |
| 16 | 16 |
Patch0: apache-tomcat-use-jks-as-inmem-keystore.patch |
| 17 |
+Patch1: apache-tomcat-fix-cve-2018-8014.patch |
|
| 17 | 18 |
BuildRequires: openjre |
| 18 | 19 |
BuildRequires: openjdk |
| 19 | 20 |
BuildRequires: apache-ant |
| ... | ... |
@@ -38,6 +39,7 @@ find . -type f \( -name "*.bat" -o -name "*.class" -o -name Thumbs.db -o -name " |
| 38 | 38 |
-name "*.jar" -o -name "*.war" -o -name "*.zip" \) -delete |
| 39 | 39 |
%setup -D -b 1 -n %{name}-%{version}-src
|
| 40 | 40 |
%patch0 -p1 |
| 41 |
+%patch1 -p1 |
|
| 41 | 42 |
|
| 42 | 43 |
%build |
| 43 | 44 |
ant -Dbase.path="../base-for-%{name}-%{version}" deploy dist-prepare dist-source
|
| ... | ... |
@@ -92,6 +94,8 @@ rm -rf %{buildroot}/*
|
| 92 | 92 |
%{_logsdir}/catalina.out
|
| 93 | 93 |
|
| 94 | 94 |
%changelog |
| 95 |
+* Mon Jun 25 2018 Srinidhi Rao <srinidhir@vmware.com> 8.5.31-2 |
|
| 96 |
+- Fix for CVE-2018-8014 |
|
| 95 | 97 |
* Mon May 07 2018 Xiaolin Li <xiaolinl@vmware.com> 8.5.31-1 |
| 96 | 98 |
- Upgraded to version 8.5.31 |
| 97 | 99 |
* Mon Apr 30 2018 Xiaolin Li <xiaolinl@vmware.com> 8.5.30-1 |