Merge Add Firewall for WebFlux
Closes gh-15967
This commit is contained in:
@@ -23,6 +23,9 @@ import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.web.server.firewall.ServerExchangeRejectedException;
|
||||
import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler;
|
||||
import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
@@ -30,6 +33,12 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
@@ -52,6 +61,78 @@ public class WebFilterChainProxyTests {
|
||||
.isNotFound();
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterWhenFirewallThenBadRequest() {
|
||||
List<WebFilter> filters = Arrays.asList(new Http200WebFilter());
|
||||
ServerWebExchangeMatcher notMatch = (exchange) -> MatchResult.notMatch();
|
||||
MatcherSecurityWebFilterChain chain = new MatcherSecurityWebFilterChain(notMatch, filters);
|
||||
WebFilterChainProxy filter = new WebFilterChainProxy(chain);
|
||||
WebTestClient.bindToController(new Object())
|
||||
.webFilter(filter)
|
||||
.build()
|
||||
.get()
|
||||
.uri("/invalid;/")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isBadRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterWhenCustomFirewallThenInvoked() {
|
||||
List<WebFilter> filters = Arrays.asList(new Http200WebFilter());
|
||||
ServerWebExchangeMatcher notMatch = (exchange) -> MatchResult.notMatch();
|
||||
MatcherSecurityWebFilterChain chain = new MatcherSecurityWebFilterChain(notMatch, filters);
|
||||
WebFilterChainProxy filter = new WebFilterChainProxy(chain);
|
||||
ServerExchangeRejectedHandler handler = mock(ServerExchangeRejectedHandler.class);
|
||||
ServerWebExchangeFirewall firewall = mock(ServerWebExchangeFirewall.class);
|
||||
filter.setFirewall(firewall);
|
||||
filter.setExchangeRejectedHandler(handler);
|
||||
WebTestClient.bindToController(new Object()).webFilter(filter).build().get().exchange();
|
||||
verify(firewall).getFirewalledExchange(any());
|
||||
verifyNoInteractions(handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterWhenCustomExchangeRejectedHandlerThenInvoked() {
|
||||
List<WebFilter> filters = Arrays.asList(new Http200WebFilter());
|
||||
ServerWebExchangeMatcher notMatch = (exchange) -> MatchResult.notMatch();
|
||||
MatcherSecurityWebFilterChain chain = new MatcherSecurityWebFilterChain(notMatch, filters);
|
||||
WebFilterChainProxy filter = new WebFilterChainProxy(chain);
|
||||
ServerExchangeRejectedHandler handler = mock(ServerExchangeRejectedHandler.class);
|
||||
ServerWebExchangeFirewall firewall = mock(ServerWebExchangeFirewall.class);
|
||||
given(firewall.getFirewalledExchange(any()))
|
||||
.willReturn(Mono.error(new ServerExchangeRejectedException("Oops")));
|
||||
filter.setFirewall(firewall);
|
||||
filter.setExchangeRejectedHandler(handler);
|
||||
WebTestClient.bindToController(new Object()).webFilter(filter).build().get().exchange();
|
||||
verify(firewall).getFirewalledExchange(any());
|
||||
verify(handler).handle(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void doFilterWhenDelayedServerExchangeRejectedException() {
|
||||
List<WebFilter> filters = Arrays.asList(new WebFilter() {
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
// simulate a delayed error (e.g. reading parameters)
|
||||
return Mono.error(new ServerExchangeRejectedException("Ooops"));
|
||||
}
|
||||
});
|
||||
ServerWebExchangeMatcher match = (exchange) -> MatchResult.match();
|
||||
MatcherSecurityWebFilterChain chain = new MatcherSecurityWebFilterChain(match, filters);
|
||||
WebFilterChainProxy filter = new WebFilterChainProxy(chain);
|
||||
ServerExchangeRejectedHandler handler = mock(ServerExchangeRejectedHandler.class);
|
||||
filter.setExchangeRejectedHandler(handler);
|
||||
// @formatter:off
|
||||
WebTestClient.bindToController(new Object())
|
||||
.webFilter(filter)
|
||||
.build()
|
||||
.get()
|
||||
.exchange();
|
||||
// @formatter:on
|
||||
verify(handler).handle(any(), any());
|
||||
}
|
||||
|
||||
static class Http200WebFilter implements WebFilter {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.server.firewall;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link StrictServerWebExchangeFirewall}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 5.7.13
|
||||
*/
|
||||
class StrictServerWebExchangeFirewallTests {
|
||||
|
||||
public String[] unnormalizedPaths = { "http://exploit.example/..", "http://exploit.example/./path/",
|
||||
"http://exploit.example/path/path/.", "http://exploit.example/path/path//.",
|
||||
"http://exploit.example/./path/../path//.", "http://exploit.example/./path",
|
||||
"http://exploit.example/.//path", "http://exploit.example/.", "http://exploit.example//path",
|
||||
"http://exploit.example//path/path", "http://exploit.example//path//path",
|
||||
"http://exploit.example/path//path" };
|
||||
|
||||
private StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
|
||||
|
||||
private MockServerHttpRequest.BaseBuilder<?> request = get("/");
|
||||
|
||||
@Test
|
||||
void cookieWhenHasNewLineThenThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ResponseCookie.from("test", "Something\nhere").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cookieWhenHasLineFeedThenThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> ResponseCookie.from("test", "Something\rhere").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void responseHeadersWhenValueHasNewLineThenThrowsException() {
|
||||
this.request = MockServerHttpRequest.get("/");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
exchange.getResponse().getHeaders().set("FOO", "new\nline");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> exchange.getResponse().setComplete().block());
|
||||
}
|
||||
|
||||
@Test
|
||||
void responseHeadersWhenValueHasLineFeedThenThrowsException() {
|
||||
this.request = MockServerHttpRequest.get("/");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
exchange.getResponse().getHeaders().set("FOO", "line\rfeed");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> exchange.getResponse().setComplete().block());
|
||||
}
|
||||
|
||||
@Test
|
||||
void responseHeadersWhenNameHasNewLineThenThrowsException() {
|
||||
this.request = MockServerHttpRequest.get("/");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
exchange.getResponse().getHeaders().set("new\nline", "FOO");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> exchange.getResponse().setComplete().block());
|
||||
}
|
||||
|
||||
@Test
|
||||
void responseHeadersWhenNameHasLineFeedThenThrowsException() {
|
||||
this.request = MockServerHttpRequest.get("/");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
exchange.getResponse().getHeaders().set("line\rfeed", "FOO");
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> exchange.getResponse().setComplete().block());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenInvalidMethodThenThrowsServerExchangeRejectedException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MockServerHttpRequest.method(HttpMethod.valueOf("INVALID"), "/"));
|
||||
}
|
||||
|
||||
private ServerWebExchange getFirewalledExchange() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(this.request.build());
|
||||
return this.firewall.getFirewalledExchange(exchange).block();
|
||||
}
|
||||
|
||||
private MockServerHttpRequest.BodyBuilder get(String uri) {
|
||||
URI url = URI.create(uri);
|
||||
return MockServerHttpRequest.method(HttpMethod.GET, url);
|
||||
}
|
||||
|
||||
// blocks XST attacks
|
||||
@Test
|
||||
void getFirewalledExchangeWhenTraceMethodThenThrowsServerExchangeRejectedException() {
|
||||
this.request = MockServerHttpRequest.method(HttpMethod.TRACE, "/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
// blocks XST attack if request is forwarded to a Microsoft IIS web server
|
||||
void getFirewalledExchangeWhenTrackMethodThenThrowsServerExchangeRejectedException() {
|
||||
this.request = MockServerHttpRequest.method(HttpMethod.TRACE, "/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
// HTTP methods are case sensitive
|
||||
void getFirewalledExchangeWhenLowercaseGetThenThrowsServerExchangeRejectedException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> HttpMethod.valueOf("get"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenAllowedThenNoException() {
|
||||
List<String> allowedMethods = Arrays.asList("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT");
|
||||
for (String allowedMethod : allowedMethods) {
|
||||
this.request = MockServerHttpRequest.method(HttpMethod.valueOf(allowedMethod), "/");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenInvalidMethodAndAnyMethodThenNoException() {
|
||||
this.firewall.setUnsafeAllowAnyHttpMethod(true);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> MockServerHttpRequest.method(HttpMethod.valueOf("INVALID"), "/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenURINotNormalizedThenThrowsServerExchangeRejectedException() {
|
||||
for (String path : this.unnormalizedPaths) {
|
||||
this.request = get(path);
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class)
|
||||
.describedAs("The path '" + path + "' is not normalized")
|
||||
.isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenSemicolonInRequestUriThenThrowsServerExchangeRejectedException() {
|
||||
this.request = get("/path;/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenEncodedSemicolonInRequestUriThenThrowsServerExchangeRejectedException() {
|
||||
this.request = get("/path%3B/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenLowercaseEncodedSemicolonInRequestUriThenThrowsServerExchangeRejectedException() {
|
||||
this.request = MockServerHttpRequest.method(HttpMethod.GET, URI.create("/path%3b/"));
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenSemicolonInRequestUriAndAllowSemicolonThenNoException() {
|
||||
this.firewall.setAllowSemicolon(true);
|
||||
this.request = get("/path;/");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenEncodedSemicolonInRequestUriAndAllowSemicolonThenNoException() {
|
||||
this.firewall.setAllowSemicolon(true);
|
||||
this.request = get("/path%3B/");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenLowercaseEncodedSemicolonInRequestUriAndAllowSemicolonThenNoException() {
|
||||
this.firewall.setAllowSemicolon(true);
|
||||
this.request = get("/path%3b/");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenLowercaseEncodedPeriodInThenThrowsServerExchangeRejectedException() {
|
||||
this.request = get("/%2e/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsLowerboundAsciiThenNoException() {
|
||||
this.request = get("/%20");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsUpperboundAsciiThenNoException() {
|
||||
this.request = get("/~");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenJapaneseCharacterThenNoException() {
|
||||
// FIXME: .method(HttpMethod.GET to .get and similar methods
|
||||
this.request = get("/\u3042");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsEncodedNullThenException() {
|
||||
this.request = get("/something%00/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsLowercaseEncodedLineFeedThenException() {
|
||||
this.request = get("/something%0a/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsUppercaseEncodedLineFeedThenException() {
|
||||
this.request = get("/something%0A/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsLowercaseEncodedCarriageReturnThenException() {
|
||||
this.request = get("/something%0d/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsUppercaseEncodedCarriageReturnThenException() {
|
||||
this.request = get("/something%0D/");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsLowercaseEncodedLineFeedAndAllowedThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedLineFeed(true);
|
||||
this.request = get("/something%0a/");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsUppercaseEncodedLineFeedAndAllowedThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedLineFeed(true);
|
||||
this.request = get("/something%0A/");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsLowercaseEncodedCarriageReturnAndAllowedThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedCarriageReturn(true);
|
||||
this.request = get("/something%0d/");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenContainsUppercaseEncodedCarriageReturnAndAllowedThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedCarriageReturn(true);
|
||||
this.request = get("/something%0D/");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
/**
|
||||
* On WebSphere 8.5 a URL like /context-root/a/b;%2f1/c can bypass a rule on /a/b/c
|
||||
* because the pathInfo is /a/b;/1/c which ends up being /a/b/1/c while Spring MVC
|
||||
* will strip the ; content from requestURI before the path is URL decoded.
|
||||
*/
|
||||
@Test
|
||||
void getFirewalledExchangeWhenLowercaseEncodedPathThenException() {
|
||||
this.request = get("/context-root/a/b;%2f1/c");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenUppercaseEncodedPathThenException() {
|
||||
this.request = get("/context-root/a/b;%2F1/c");
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenAllowUrlEncodedSlashAndLowercaseEncodedPathThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.firewall.setAllowSemicolon(true);
|
||||
this.request = get("/context-root/a/b;%2f1/c");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenAllowUrlEncodedSlashAndUppercaseEncodedPathThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.firewall.setAllowSemicolon(true);
|
||||
this.request = get("/context-root/a/b;%2F1/c");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenAllowUrlLowerCaseEncodedDoubleSlashThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.firewall.setAllowUrlEncodedDoubleSlash(true);
|
||||
this.request = get("/context-root/a/b%2f%2fc");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenAllowUrlUpperCaseEncodedDoubleSlashThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.firewall.setAllowUrlEncodedDoubleSlash(true);
|
||||
this.request = get("/context-root/a/b%2F%2Fc");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenAllowUrlLowerCaseAndUpperCaseEncodedDoubleSlashThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.firewall.setAllowUrlEncodedDoubleSlash(true);
|
||||
this.request = get("/context-root/a/b%2f%2Fc");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenAllowUrlUpperCaseAndLowerCaseEncodedDoubleSlashThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.firewall.setAllowUrlEncodedDoubleSlash(true);
|
||||
this.request = get("/context-root/a/b%2F%2fc");
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenRemoveFromUpperCaseEncodedUrlBlocklistThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.request = get("/context-root/a/b%2Fc");
|
||||
this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2F%2F"));
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenRemoveFromDecodedUrlBlocklistThenNoException() {
|
||||
this.request = get("/a/b%2F%2Fc");
|
||||
this.firewall.getDecodedUrlBlocklist().removeAll(Arrays.asList("//"));
|
||||
this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2F%2F"));
|
||||
this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2F"));
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenTrustedDomainThenNoException() {
|
||||
this.request.header("Host", "example.org");
|
||||
this.firewall.setAllowedHostnames((hostname) -> hostname.equals("example.org"));
|
||||
getFirewalledExchange();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenUntrustedDomainThenException() {
|
||||
this.request = get("https://example.org");
|
||||
this.firewall.setAllowedHostnames((hostname) -> hostname.equals("myexample.org"));
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> getFirewalledExchange());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeaderWhenNotAllowedHeaderNameThenException() {
|
||||
this.firewall.setAllowedHeaderNames((name) -> !name.equals("bad name"));
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("bad name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenHeaderNameNotAllowedWithAugmentedHeaderNamesThenException() {
|
||||
this.firewall.setAllowedHeaderNames(
|
||||
StrictServerWebExchangeFirewall.ALLOWED_HEADER_NAMES.and((name) -> !name.equals("bad name")));
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.getFirst("bad name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeaderWhenNotAllowedHeaderValueThenException() {
|
||||
this.request.header("good name", "bad value");
|
||||
this.firewall.setAllowedHeaderValues((value) -> !value.equals("bad value"));
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("good name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeWhenHeaderValueNotAllowedWithAugmentedHeaderValuesThenException() {
|
||||
this.request.header("good name", "bad value");
|
||||
this.firewall.setAllowedHeaderValues(
|
||||
StrictServerWebExchangeFirewall.ALLOWED_HEADER_VALUES.and((value) -> !value.equals("bad value")));
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("good name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetDateHeaderWhenControlCharacterInHeaderNameThenException() {
|
||||
this.request.header("Bad\0Name", "some value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Bad\0Name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeaderWhenUndefinedCharacterInHeaderNameThenException() {
|
||||
this.request.header("Bad\uFFFEName", "some value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Bad\uFFFEName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeadersWhenControlCharacterInHeaderNameThenException() {
|
||||
this.request.header("Bad\0Name", "some value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Bad\0Name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeaderNamesWhenControlCharacterInHeaderNameThenException() {
|
||||
this.request.header("Bad\0Name", "some value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class)
|
||||
.isThrownBy(() -> headers.keySet().iterator().next());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeaderWhenControlCharacterInHeaderValueThenException() {
|
||||
this.request.header("Something", "bad\0value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Something"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeaderWhenHorizontalTabInHeaderValueThenNoException() {
|
||||
this.request.header("Something", "tab\tvalue");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThat(headers.getFirst("Something")).isEqualTo("tab\tvalue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeaderWhenUndefinedCharacterInHeaderValueThenException() {
|
||||
this.request.header("Something", "bad\uFFFEvalue");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class)
|
||||
.isThrownBy(() -> headers.getFirst("Something"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeadersWhenControlCharacterInHeaderValueThenException() {
|
||||
this.request.header("Something", "bad\0value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
HttpHeaders headers = exchange.getRequest().getHeaders();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> headers.get("Something"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetParameterWhenControlCharacterInParameterNameThenException() {
|
||||
this.request.queryParam("Bad\0Name", "some value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(request::getQueryParams);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetParameterValuesWhenNotAllowedInParameterValueThenException() {
|
||||
this.firewall.setAllowedParameterValues((value) -> !value.equals("bad value"));
|
||||
this.request.queryParam("Something", "bad value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> request.getQueryParams());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFirewalledExchangeGetParameterValuesWhenNotAllowedInParameterNameThenException() {
|
||||
this.firewall.setAllowedParameterNames((value) -> !value.equals("bad name"));
|
||||
this.request.queryParam("bad name", "good value");
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
assertThatExceptionOfType(ServerExchangeRejectedException.class).isThrownBy(() -> request.getQueryParams());
|
||||
}
|
||||
|
||||
// gh-9598
|
||||
@Test
|
||||
void getFirewalledExchangeGetHeaderWhenNameIsNullThenNull() {
|
||||
ServerWebExchange exchange = getFirewalledExchange();
|
||||
assertThat(exchange.getRequest().getHeaders().get(null)).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user