Skip to content

Commit e4e7363

Browse files
eddumelendezjzheaux
authored andcommitted
Add support for allowedHostnames in StrictHttpFirewall
Introduce a new method `setAllowedHostnames` which perform the validation against untrusted hostnames. Fixes gh-4310
1 parent 75e2483 commit e4e7363

File tree

3 files changed

+79
-5
lines changed

3 files changed

+79
-5
lines changed

web/src/main/java/org/springframework/security/web/FilterInvocation.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,15 @@ public String getQueryString() {
228228
public void setQueryString(String queryString) {
229229
this.queryString = queryString;
230230
}
231+
232+
@Override
233+
public String getServerName() {
234+
return null;
235+
}
231236
}
232237

233238
final class UnsupportedOperationExceptionInvocationHandler implements InvocationHandler {
234239
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
235240
throw new UnsupportedOperationException(method + " is not supported");
236241
}
237-
}
242+
}

web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,14 +16,14 @@
1616

1717
package org.springframework.security.web.firewall;
1818

19-
import javax.servlet.http.HttpServletRequest;
20-
import javax.servlet.http.HttpServletResponse;
2119
import java.util.Arrays;
2220
import java.util.Collection;
2321
import java.util.Collections;
2422
import java.util.HashSet;
2523
import java.util.List;
2624
import java.util.Set;
25+
import javax.servlet.http.HttpServletRequest;
26+
import javax.servlet.http.HttpServletResponse;
2727

2828
/**
2929
* <p>
@@ -59,10 +59,15 @@
5959
* Rejects URLs that contain a URL encoded percent. See
6060
* {@link #setAllowUrlEncodedPercent(boolean)}
6161
* </li>
62+
* <li>
63+
* Rejects hosts that are not allowed. See
64+
* {@link #setAllowedHostnames(Collection)}
65+
* </li>
6266
* </ul>
6367
*
6468
* @see DefaultHttpFirewall
6569
* @author Rob Winch
70+
* @author Eddú Meléndez
6671
* @since 4.2.4
6772
*/
6873
public class StrictHttpFirewall implements HttpFirewall {
@@ -82,6 +87,8 @@ public class StrictHttpFirewall implements HttpFirewall {
8287

8388
private Set<String> decodedUrlBlacklist = new HashSet<String>();
8489

90+
private Collection<String> allowedHostnames;
91+
8592
public StrictHttpFirewall() {
8693
urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
8794
urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
@@ -230,6 +237,13 @@ public void setAllowUrlEncodedPercent(boolean allowUrlEncodedPercent) {
230237
}
231238
}
232239

240+
public void setAllowedHostnames(Collection<String> allowedHostnames) {
241+
if (allowedHostnames == null) {
242+
throw new IllegalArgumentException("allowedHostnames cannot be null");
243+
}
244+
this.allowedHostnames = allowedHostnames;
245+
}
246+
233247
private void urlBlacklistsAddAll(Collection<String> values) {
234248
this.encodedUrlBlacklist.addAll(values);
235249
this.decodedUrlBlacklist.addAll(values);
@@ -243,6 +257,7 @@ private void urlBlacklistsRemoveAll(Collection<String> values) {
243257
@Override
244258
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
245259
rejectedBlacklistedUrls(request);
260+
rejectedUntrustedHosts(request);
246261

247262
if (!isNormalized(request)) {
248263
throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
@@ -272,6 +287,19 @@ private void rejectedBlacklistedUrls(HttpServletRequest request) {
272287
}
273288
}
274289

290+
private void rejectedUntrustedHosts(HttpServletRequest request) {
291+
String serverName = request.getServerName();
292+
if (serverName == null) {
293+
return;
294+
}
295+
if (this.allowedHostnames == null) {
296+
return;
297+
}
298+
if (!this.allowedHostnames.contains(serverName)) {
299+
throw new RequestRejectedException("The request was rejected because the domain " + serverName + " is untrusted.");
300+
}
301+
}
302+
275303
@Override
276304
public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
277305
return new FirewalledResponse(response);

web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,13 +16,16 @@
1616

1717
package org.springframework.security.web.firewall;
1818

19+
import java.util.Arrays;
20+
1921
import org.junit.Test;
2022
import org.springframework.mock.web.MockHttpServletRequest;
2123

2224
import static org.assertj.core.api.Assertions.fail;
2325

2426
/**
2527
* @author Rob Winch
28+
* @author Eddú Meléndez
2629
*/
2730
public class StrictHttpFirewallTests {
2831
public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.",
@@ -373,4 +376,42 @@ public void getFirewalledRequestWhenAllowUrlEncodedSlashAndUppercaseEncodedPathT
373376

374377
this.firewall.getFirewalledRequest(request);
375378
}
379+
380+
@Test
381+
public void getFirewalledRequestWhenTrustedDomainThenNoException() {
382+
String host = "example.org";
383+
this.request.addHeader("Host", host);
384+
this.firewall.setAllowedHostnames(Arrays.asList(host));
385+
386+
try {
387+
this.firewall.getFirewalledRequest(this.request);
388+
} catch (RequestRejectedException fail) {
389+
fail("Host " + host + " was rejected");
390+
}
391+
}
392+
393+
@Test
394+
public void getFirewalledRequestWhenUntrustedDomainThenException() {
395+
String host = "example.org";
396+
this.request.addHeader("Host", host);
397+
this.firewall.setAllowedHostnames(Arrays.asList("myexample.org"));
398+
399+
try {
400+
this.firewall.getFirewalledRequest(this.request);
401+
fail("Host " + host + " was accepted");
402+
} catch (RequestRejectedException expected) {
403+
}
404+
}
405+
406+
@Test
407+
public void getFirewalledRequestWhenDefaultsThenNoException() {
408+
String host = "example.org";
409+
this.request.addHeader("Host", host);
410+
411+
try {
412+
this.firewall.getFirewalledRequest(this.request);
413+
} catch (RequestRejectedException fail) {
414+
fail("Host " + host + " was rejected");
415+
}
416+
}
376417
}

0 commit comments

Comments
 (0)