Skip to content

Commit 52c80c7

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 ded83cc commit 52c80c7

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-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.
@@ -26,6 +26,7 @@
2626
import java.util.HashSet;
2727
import java.util.List;
2828
import java.util.Set;
29+
import java.util.function.Predicate;
2930

3031
/**
3132
* <p>
@@ -66,10 +67,15 @@
6667
* Rejects URLs that contain a URL encoded percent. See
6768
* {@link #setAllowUrlEncodedPercent(boolean)}
6869
* </li>
70+
* <li>
71+
* Rejects hosts that are not allowed. See
72+
* {@link #setAllowedHostnames(Predicate)}
73+
* </li>
6974
* </ul>
7075
*
7176
* @see DefaultHttpFirewall
7277
* @author Rob Winch
78+
* @author Eddú Meléndez
7379
* @since 4.2.4
7480
*/
7581
public class StrictHttpFirewall implements HttpFirewall {
@@ -96,6 +102,8 @@ public class StrictHttpFirewall implements HttpFirewall {
96102

97103
private Set<String> allowedHttpMethods = createDefaultAllowedHttpMethods();
98104

105+
private Predicate<String> allowedHostnames = hostname -> true;
106+
99107
public StrictHttpFirewall() {
100108
urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
101109
urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
@@ -277,6 +285,13 @@ public void setAllowUrlEncodedPercent(boolean allowUrlEncodedPercent) {
277285
}
278286
}
279287

288+
public void setAllowedHostnames(Predicate<String> allowedHostnames) {
289+
if (allowedHostnames == null) {
290+
throw new IllegalArgumentException("allowedHostnames cannot be null");
291+
}
292+
this.allowedHostnames = allowedHostnames;
293+
}
294+
280295
private void urlBlacklistsAddAll(Collection<String> values) {
281296
this.encodedUrlBlacklist.addAll(values);
282297
this.decodedUrlBlacklist.addAll(values);
@@ -291,6 +306,7 @@ private void urlBlacklistsRemoveAll(Collection<String> values) {
291306
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
292307
rejectForbiddenHttpMethod(request);
293308
rejectedBlacklistedUrls(request);
309+
rejectedUntrustedHosts(request);
294310

295311
if (!isNormalized(request)) {
296312
throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
@@ -332,6 +348,13 @@ private void rejectedBlacklistedUrls(HttpServletRequest request) {
332348
}
333349
}
334350

351+
private void rejectedUntrustedHosts(HttpServletRequest request) {
352+
String serverName = request.getServerName();
353+
if (serverName != null && !this.allowedHostnames.test(serverName)) {
354+
throw new RequestRejectedException("The request was rejected because the domain " + serverName + " is untrusted.");
355+
}
356+
}
357+
335358
@Override
336359
public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
337360
return new FirewalledResponse(response);

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

+18-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.
@@ -29,6 +29,7 @@
2929

3030
/**
3131
* @author Rob Winch
32+
* @author Eddú Meléndez
3233
*/
3334
public class StrictHttpFirewallTests {
3435
public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.",
@@ -428,4 +429,20 @@ public void getFirewalledRequestWhenAllowUrlEncodedSlashAndUppercaseEncodedPathT
428429

429430
this.firewall.getFirewalledRequest(request);
430431
}
432+
433+
@Test
434+
public void getFirewalledRequestWhenTrustedDomainThenNoException() {
435+
this.request.addHeader("Host", "example.org");
436+
this.firewall.setAllowedHostnames(hostname -> hostname.equals("example.org"));
437+
438+
assertThatCode(() -> this.firewall.getFirewalledRequest(this.request)).doesNotThrowAnyException();
439+
}
440+
441+
@Test(expected = RequestRejectedException.class)
442+
public void getFirewalledRequestWhenUntrustedDomainThenException() {
443+
this.request.addHeader("Host", "example.org");
444+
this.firewall.setAllowedHostnames(hostname -> hostname.equals("myexample.org"));
445+
446+
this.firewall.getFirewalledRequest(this.request);
447+
}
431448
}

0 commit comments

Comments
 (0)