Skip to content

Commit 3e5b65f

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 c62789c commit 3e5b65f

File tree

3 files changed

+58
-3
lines changed

3 files changed

+58
-3
lines changed

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

+6-1
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

+24-1
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.
@@ -24,6 +24,7 @@
2424
import java.util.HashSet;
2525
import java.util.List;
2626
import java.util.Set;
27+
import java.util.function.Predicate;
2728

2829
/**
2930
* <p>
@@ -59,10 +60,15 @@
5960
* Rejects URLs that contain a URL encoded percent. See
6061
* {@link #setAllowUrlEncodedPercent(boolean)}
6162
* </li>
63+
* <li>
64+
* Rejects hosts that are not allowed. See
65+
* {@link #setAllowedHostnames(Predicate)}
66+
* </li>
6267
* </ul>
6368
*
6469
* @see DefaultHttpFirewall
6570
* @author Rob Winch
71+
* @author Eddú Meléndez
6672
* @since 5.0.1
6773
*/
6874
public class StrictHttpFirewall implements HttpFirewall {
@@ -82,6 +88,8 @@ public class StrictHttpFirewall implements HttpFirewall {
8288

8389
private Set<String> decodedUrlBlacklist = new HashSet<String>();
8490

91+
private Predicate<String> allowedHostnames = hostname -> true;
92+
8593
public StrictHttpFirewall() {
8694
urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
8795
urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
@@ -230,6 +238,13 @@ public void setAllowUrlEncodedPercent(boolean allowUrlEncodedPercent) {
230238
}
231239
}
232240

241+
public void setAllowedHostnames(Predicate<String> allowedHostnames) {
242+
if (allowedHostnames == null) {
243+
throw new IllegalArgumentException("allowedHostnames cannot be null");
244+
}
245+
this.allowedHostnames = allowedHostnames;
246+
}
247+
233248
private void urlBlacklistsAddAll(Collection<String> values) {
234249
this.encodedUrlBlacklist.addAll(values);
235250
this.decodedUrlBlacklist.addAll(values);
@@ -243,6 +258,7 @@ private void urlBlacklistsRemoveAll(Collection<String> values) {
243258
@Override
244259
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
245260
rejectedBlacklistedUrls(request);
261+
rejectedUntrustedHosts(request);
246262

247263
if (!isNormalized(request)) {
248264
throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
@@ -272,6 +288,13 @@ private void rejectedBlacklistedUrls(HttpServletRequest request) {
272288
}
273289
}
274290

291+
private void rejectedUntrustedHosts(HttpServletRequest request) {
292+
String serverName = request.getServerName();
293+
if (serverName != null && !this.allowedHostnames.test(serverName)) {
294+
throw new RequestRejectedException("The request was rejected because the domain " + serverName + " is untrusted.");
295+
}
296+
}
297+
275298
@Override
276299
public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
277300
return new FirewalledResponse(response);

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

+28-1
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.
@@ -23,6 +23,7 @@
2323

2424
/**
2525
* @author Rob Winch
26+
* @author Eddú Meléndez
2627
*/
2728
public class StrictHttpFirewallTests {
2829
public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.",
@@ -373,4 +374,30 @@ public void getFirewalledRequestWhenAllowUrlEncodedSlashAndUppercaseEncodedPathT
373374

374375
this.firewall.getFirewalledRequest(request);
375376
}
377+
378+
@Test
379+
public void getFirewalledRequestWhenTrustedDomainThenNoException() {
380+
String host = "example.org";
381+
this.request.addHeader("Host", host);
382+
this.firewall.setAllowedHostnames(hostname -> hostname.equals("example.org"));
383+
384+
try {
385+
this.firewall.getFirewalledRequest(this.request);
386+
} catch (RequestRejectedException fail) {
387+
fail("Host " + host + " was rejected");
388+
}
389+
}
390+
391+
@Test
392+
public void getFirewalledRequestWhenUntrustedDomainThenException() {
393+
String host = "example.org";
394+
this.request.addHeader("Host", host);
395+
this.firewall.setAllowedHostnames(hostname -> hostname.equals("myexample.org"));
396+
397+
try {
398+
this.firewall.getFirewalledRequest(this.request);
399+
fail("Host " + host + " was accepted");
400+
} catch (RequestRejectedException expected) {
401+
}
402+
}
376403
}

0 commit comments

Comments
 (0)