Add Cross Site Tracing (XST) & HTTP Method Tampering Protection
Fixes: gh-5377
This commit is contained in:
parent
2c92496911
commit
73345e7434
|
@ -15,7 +15,10 @@
|
|||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.core.userdetails.PasswordEncodedUser
|
||||
import org.springframework.security.web.firewall.StrictHttpFirewall
|
||||
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
|
||||
|
@ -44,7 +47,7 @@ class CsrfConfigurerTests extends BaseSpringSpec {
|
|||
@Unroll
|
||||
def "csrf applied by default"() {
|
||||
setup:
|
||||
loadConfig(CsrfAppliedDefaultConfig)
|
||||
loadConfig(CsrfAppliedDefaultConfig, AllowHttpMethodsFirewallConfig)
|
||||
request.method = httpMethod
|
||||
clearCsrfToken()
|
||||
when:
|
||||
|
@ -66,11 +69,21 @@ class CsrfConfigurerTests extends BaseSpringSpec {
|
|||
|
||||
def "csrf default creates CsrfRequestDataValueProcessor"() {
|
||||
when:
|
||||
loadConfig(CsrfAppliedDefaultConfig)
|
||||
loadConfig(CsrfAppliedDefaultConfig, AllowHttpMethodsFirewallConfig)
|
||||
then:
|
||||
context.getBean(RequestDataValueProcessor)
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class AllowHttpMethodsFirewallConfig {
|
||||
@Bean
|
||||
StrictHttpFirewall strictHttpFirewall() {
|
||||
StrictHttpFirewall result = new StrictHttpFirewall();
|
||||
result.setAllowedHttpMethods(StrictHttpFirewall.ALLOW_ANY_HTTP_METHOD);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class CsrfAppliedDefaultConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ public class NamespaceHttpFirewallTests extends BaseSpringSpec {
|
|||
MockFilterChain chain
|
||||
|
||||
def setup() {
|
||||
request = new MockHttpServletRequest()
|
||||
request = new MockHttpServletRequest("GET", "")
|
||||
response = new MockHttpServletResponse()
|
||||
chain = new MockFilterChain()
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ public class NamespaceHttpPortMappingsTests extends BaseSpringSpec {
|
|||
MockFilterChain chain
|
||||
|
||||
def setup() {
|
||||
request = new MockHttpServletRequest()
|
||||
request = new MockHttpServletRequest("GET", "")
|
||||
request.setMethod("GET")
|
||||
response = new MockHttpServletResponse()
|
||||
chain = new MockFilterChain()
|
||||
|
|
|
@ -371,7 +371,7 @@ public class NamespaceRememberMeTests extends BaseSpringSpec {
|
|||
}
|
||||
|
||||
Cookie createRememberMeCookie() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest()
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
super.setupCsrf("CSRF_TOKEN", request, response)
|
||||
|
||||
|
|
|
@ -270,7 +270,7 @@ public class RememberMeConfigurerTests extends BaseSpringSpec {
|
|||
}
|
||||
|
||||
Cookie createRememberMeCookie() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest()
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
super.setupCsrf("CSRF_TOKEN", request, response)
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
|
|||
}
|
||||
|
||||
FilterInvocation createFilterinvocation(String path, String method) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.setMethod(method);
|
||||
request.setRequestURI(null);
|
||||
request.setServletPath(path);
|
||||
|
|
|
@ -69,7 +69,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, defaultHeaders)
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, defaultHeaders)
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
|
||||
|
@ -131,7 +131,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
expect:
|
||||
assertHeaders(response, ['X-Content-Type-Options':'nosniff'])
|
||||
|
@ -147,7 +147,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
expect:
|
||||
assertHeaders(response, ['X-Frame-Options':'DENY'])
|
||||
|
@ -163,7 +163,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
expect:
|
||||
assertHeaders(response, ['X-Frame-Options':'DENY'])
|
||||
|
@ -179,7 +179,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
expect:
|
||||
assertHeaders(response, ['X-Frame-Options':'SAMEORIGIN'])
|
||||
|
@ -228,7 +228,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
then:
|
||||
assertHeaders(response, ['X-Frame-Options':'ALLOW-FROM https://example.com'])
|
||||
|
@ -246,7 +246,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
|
||||
def request = new MockHttpServletRequest()
|
||||
def request = new MockHttpServletRequest("GET", "")
|
||||
request.setParameter("from", "https://example.com");
|
||||
hf.doFilter(request, response, new MockFilterChain())
|
||||
|
||||
|
@ -265,7 +265,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
then:
|
||||
assertHeaders(response, ['a':'b'])
|
||||
|
@ -283,7 +283,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
then:
|
||||
assertHeaders(response , ['a':'b', 'c':'d'])
|
||||
|
@ -304,7 +304,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['abc':'def'])
|
||||
}
|
||||
|
@ -346,7 +346,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
then:
|
||||
assertHeaders(response, ['X-XSS-Protection':'1; mode=block'])
|
||||
|
@ -363,7 +363,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
then:
|
||||
assertHeaders(response, ['X-XSS-Protection':'1; mode=block'])
|
||||
|
@ -380,7 +380,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
|
||||
then:
|
||||
assertHeaders(response, ['X-XSS-Protection':'0'])
|
||||
|
@ -413,7 +413,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
|
||||
'Expires' : '0',
|
||||
|
@ -431,7 +431,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains'])
|
||||
}
|
||||
|
@ -447,7 +447,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
then:
|
||||
response.headerNames.empty
|
||||
}
|
||||
|
@ -465,7 +465,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Strict-Transport-Security': 'max-age=1'])
|
||||
}
|
||||
|
@ -515,7 +515,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
|
||||
}
|
||||
|
@ -535,7 +535,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
|
||||
}
|
||||
|
@ -555,7 +555,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
then:
|
||||
response.headerNames.empty
|
||||
}
|
||||
|
@ -575,7 +575,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=604800 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
|
||||
}
|
||||
|
@ -595,7 +595,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Public-Key-Pins': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="'])
|
||||
}
|
||||
|
@ -615,7 +615,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; includeSubDomains'])
|
||||
}
|
||||
|
@ -635,7 +635,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; report-uri="http://example.net/pkp-report"'])
|
||||
}
|
||||
|
@ -657,7 +657,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
expectedHeaders.remove('Expires')
|
||||
expectedHeaders.remove('Pragma')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
@ -675,7 +675,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('X-Content-Type-Options')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
@ -693,7 +693,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('Strict-Transport-Security')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
@ -714,7 +714,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
@ -732,7 +732,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('X-Frame-Options')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
@ -750,7 +750,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders.remove('X-XSS-Protection')
|
||||
when:
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, expectedHeaders)
|
||||
}
|
||||
|
@ -853,7 +853,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders['Content-Security-Policy'] = 'default-src \'self\''
|
||||
then:
|
||||
|
@ -885,7 +885,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Content-Security-Policy':'default-src \'self\''])
|
||||
}
|
||||
|
@ -913,7 +913,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
|
||||
def expectedHeaders = [:] << defaultHeaders
|
||||
expectedHeaders['Content-Security-Policy-Report-Only'] = 'default-src https:; report-uri https://example.com/'
|
||||
then:
|
||||
|
@ -931,7 +931,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Referrer-Policy': 'no-referrer'])
|
||||
}
|
||||
|
@ -947,7 +947,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
|
|||
when:
|
||||
def hf = getFilter(HeaderWriterFilter)
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
|
||||
hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
|
||||
then:
|
||||
assertHeaders(response, ['Referrer-Policy': 'same-origin'])
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
|
|||
createAppContext()
|
||||
then:
|
||||
Filter debugFilter = appContext.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest()
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
|
||||
request.setServletPath("/unprotected");
|
||||
debugFilter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
|
||||
request.setServletPath("/nomatch");
|
||||
|
|
|
@ -93,7 +93,7 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
|
|||
UserDetailsService uds = appContext.getBean('uds')
|
||||
UserDetailsService uds2 = appContext.getBean('uds2')
|
||||
when:
|
||||
MockHttpServletRequest request = new MockHttpServletRequest()
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
MockFilterChain chain = new MockFilterChain()
|
||||
request.servletPath = "/first/login"
|
||||
|
@ -104,7 +104,7 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
|
|||
verify(uds).loadUserByUsername(anyString()) || true
|
||||
verifyZeroInteractions(uds2) || true
|
||||
when:
|
||||
MockHttpServletRequest request2 = new MockHttpServletRequest()
|
||||
MockHttpServletRequest request2 = new MockHttpServletRequest("GET", "")
|
||||
MockHttpServletResponse response2 = new MockHttpServletResponse()
|
||||
MockFilterChain chain2 = new MockFilterChain()
|
||||
request2.servletPath = "/login"
|
||||
|
|
|
@ -115,7 +115,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
createAppContext()
|
||||
SessionRegistry registry = appContext.getBean(SessionRegistry)
|
||||
registry.registerNewSession("1", new User("user","password",AuthorityUtils.createAuthorityList("ROLE_USER")))
|
||||
MockHttpServletRequest request = new MockHttpServletRequest()
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
String credentials = "user:password"
|
||||
request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64())
|
||||
|
@ -134,7 +134,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
}
|
||||
}
|
||||
createAppContext()
|
||||
MockHttpServletRequest request = new MockHttpServletRequest()
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
|
||||
MockHttpServletResponse response = new MockHttpServletResponse()
|
||||
String originalSessionId = request.session.id
|
||||
String credentials = "user:password"
|
||||
|
@ -282,7 +282,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
mockBean(SessionAuthenticationStrategy,'ss')
|
||||
createAppContext()
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.getSession();
|
||||
request.servletPath = "/login"
|
||||
request.setMethod("POST");
|
||||
|
@ -343,15 +343,15 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
|
|||
}
|
||||
};
|
||||
when: "First session is established"
|
||||
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||
seshFilter.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain());
|
||||
then: "ok"
|
||||
mockResponse.redirectedUrl == null
|
||||
when: "Second session is established"
|
||||
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||
seshFilter.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain());
|
||||
then: "ok"
|
||||
mockResponse.redirectedUrl == null
|
||||
when: "Third session is established"
|
||||
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||
seshFilter.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain());
|
||||
then: "Rejected"
|
||||
mockResponse.redirectedUrl == "/max-exceeded";
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ public class FilterChainProxyConfigTests {
|
|||
}
|
||||
|
||||
private void doNormalOperation(FilterChainProxy filterChainProxy) throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.setServletPath("/foo/secure/super/somefile.html");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
|
|
@ -54,7 +54,7 @@ public class WebSecurityTests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setMethod("GET");
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.chain = new MockFilterChain();
|
||||
|
|
|
@ -274,7 +274,7 @@ public class WebSecurityConfigurationTests {
|
|||
public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() throws Exception {
|
||||
this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "notused");
|
||||
FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest(), new MockHttpServletResponse(), new MockFilterChain());
|
||||
FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest("GET", ""), new MockHttpServletResponse(), new MockFilterChain());
|
||||
|
||||
AbstractSecurityExpressionHandler handler = this.spring.getContext().getBean(AbstractSecurityExpressionHandler.class);
|
||||
EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, invocation);
|
||||
|
|
|
@ -68,7 +68,7 @@ public class AuthorizeRequestsTests {
|
|||
@Before
|
||||
public void setup() {
|
||||
this.servletContext = spy(new MockServletContext());
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setMethod("GET");
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.chain = new MockFilterChain();
|
||||
|
|
|
@ -51,7 +51,7 @@ public class HttpSecurityAntMatchersTests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
request = new MockHttpServletRequest("GET", "");
|
||||
response = new MockHttpServletResponse();
|
||||
chain = new MockFilterChain();
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ public class HttpSecurityLogoutTests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
request = new MockHttpServletRequest("GET", "");
|
||||
response = new MockHttpServletResponse();
|
||||
chain = new MockFilterChain();
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ public class HttpSecurityRequestMatchersTests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setMethod("GET");
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.chain = new MockFilterChain();
|
||||
|
|
|
@ -72,7 +72,7 @@ public class SessionManagementConfigurerServlet31Tests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
request = new MockHttpServletRequest("GET", "");
|
||||
response = new MockHttpServletResponse();
|
||||
chain = new MockFilterChain();
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ public class SessionManagementConfigurerServlet31Tests {
|
|||
public void changeSessionIdDefaultsInServlet31Plus() throws Exception {
|
||||
spy(ReflectionUtils.class);
|
||||
Method method = mock(Method.class);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.getSession();
|
||||
request.setServletPath("/login");
|
||||
request.setMethod("POST");
|
||||
|
|
|
@ -55,7 +55,7 @@ public class UrlAuthorizationConfigurerTests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setMethod("GET");
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.chain = new MockFilterChain();
|
||||
|
@ -211,4 +211,4 @@ public class UrlAuthorizationConfigurerTests {
|
|||
|
||||
this.context.getAutowireCapableBeanFactory().autowireBean(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ public class OAuth2ClientConfigurerTests {
|
|||
|
||||
AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
|
||||
new HttpSessionOAuth2AuthorizationRequestRepository();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ public class OAuth2LoginConfigurerTests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.filterChain = new MockFilterChain();
|
||||
|
||||
|
|
|
@ -493,7 +493,7 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
|
|||
}
|
||||
|
||||
private MockHttpServletRequest sockjsHttpRequest(String mapping) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.setMethod("GET");
|
||||
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE,
|
||||
"/289/tpyx6mde/websocket");
|
||||
|
|
|
@ -65,7 +65,7 @@ public class GrantedAuthorityDefaultsJcTests {
|
|||
public void setup() {
|
||||
setup("USER");
|
||||
|
||||
request = new MockHttpServletRequest();
|
||||
request = new MockHttpServletRequest("GET", "");
|
||||
request.setMethod("GET");
|
||||
response = new MockHttpServletResponse();
|
||||
chain = new MockFilterChain();
|
||||
|
|
|
@ -58,7 +58,7 @@ public class GrantedAuthorityDefaultsXmlTests {
|
|||
public void setup() {
|
||||
setup("USER");
|
||||
|
||||
request = new MockHttpServletRequest();
|
||||
request = new MockHttpServletRequest("GET", "");
|
||||
request.setMethod("GET");
|
||||
response = new MockHttpServletResponse();
|
||||
chain = new MockFilterChain();
|
||||
|
|
|
@ -123,7 +123,7 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
|
|||
}
|
||||
|
||||
private FilterInvocation createFilterInvocation(String path, String method) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.setRequestURI(null);
|
||||
request.setMethod(method);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ public class NamespaceHttpBasicTests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setMethod("GET");
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.chain = new MockFilterChain();
|
||||
|
|
|
@ -73,7 +73,7 @@ public class SessionManagementConfigServlet31Tests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
request = new MockHttpServletRequest("GET", "");
|
||||
response = new MockHttpServletResponse();
|
||||
chain = new MockFilterChain();
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ public class SessionManagementConfigServlet31Tests {
|
|||
public void changeSessionIdDefaultsInServlet31Plus() throws Exception {
|
||||
spy(ReflectionUtils.class);
|
||||
Method method = mock(Method.class);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.getSession();
|
||||
request.setServletPath("/login");
|
||||
request.setMethod("POST");
|
||||
|
@ -112,7 +112,7 @@ public class SessionManagementConfigServlet31Tests {
|
|||
public void changeSessionId() throws Exception {
|
||||
spy(ReflectionUtils.class);
|
||||
Method method = mock(Method.class);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.getSession();
|
||||
request.setServletPath("/login");
|
||||
request.setMethod("POST");
|
||||
|
|
|
@ -55,7 +55,7 @@ public class CustomHttpSecurityConfigurerTests {
|
|||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = new MockHttpServletRequest();
|
||||
request = new MockHttpServletRequest("GET", "");
|
||||
response = new MockHttpServletResponse();
|
||||
chain = new MockFilterChain();
|
||||
request.setMethod("GET");
|
||||
|
|
|
@ -18,10 +18,15 @@
|
|||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http-firewall ref="firewall"/>
|
||||
<http auto-config="true"/>
|
||||
|
||||
<b:import resource="CsrfConfigTests-shared-userservice.xml"/>
|
||||
|
||||
<b:bean id="firewall" class="org.springframework.security.web.firewall.StrictHttpFirewall"
|
||||
p:unsafeAllowAnyHttpMethod="true"/>
|
||||
</b:beans>
|
||||
|
|
|
@ -18,13 +18,18 @@
|
|||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<http-firewall ref="firewall"/>
|
||||
<http auto-config="true">
|
||||
<intercept-url pattern="/authenticated/**" access="authenticated"/>
|
||||
<csrf/>
|
||||
</http>
|
||||
|
||||
<b:import resource="CsrfConfigTests-shared-userservice.xml"/>
|
||||
|
||||
<b:bean id="firewall" class="org.springframework.security.web.firewall.StrictHttpFirewall"
|
||||
p:unsafeAllowAnyHttpMethod="true"/>
|
||||
</b:beans>
|
||||
|
|
|
@ -179,6 +179,45 @@ public StrictHttpFirewall httpFirewall() {
|
|||
}
|
||||
----
|
||||
|
||||
The `StrictHttpFirewall` provides a whitelist of valid HTTP methods that are allowed to protect against https://www.owasp.org/index.php/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)[HTTP Verb Tampering].
|
||||
The default valid methods are "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", and "PUT".
|
||||
If your application needs to modify the valid methods, you can configure a custom `StrictHttpFirewall` bean.
|
||||
For example, the following will only allow HTTP "GET" and "POST" methods:
|
||||
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<b:bean id="httpFirewall"
|
||||
class="org.springframework.security.web.firewall.StrictHttpFirewall"
|
||||
p:allowedHttpMethods="GET,HEAD"/>
|
||||
|
||||
<http-firewall ref="httpFirewall"/>
|
||||
----
|
||||
|
||||
The same thing can be achieved with Java Configuration by exposing a `StrictHttpFirewall` bean.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public StrictHttpFirewall httpFirewall() {
|
||||
StrictHttpFirewall firewall = new StrictHttpFirewall();
|
||||
firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
|
||||
return firewall;
|
||||
}
|
||||
----
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you are using `new MockHttpServletRequest()` it currently creates an HTTP method as an empty String "".
|
||||
This is an invalid HTTP method and will be rejected by Spring Security.
|
||||
You can resolve this by replacing it with `new MockHttpServletRequest("GET", "")`.
|
||||
See https://jira.spring.io/browse/SPR-16851[SPR_16851] for an issue requesting to improve this.
|
||||
====
|
||||
|
||||
If you must allow any HTTP method (not recommended), you can use `StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)`.
|
||||
This will disable validation of the HTTP method entirely.
|
||||
|
||||
|
||||
=== Use with other Filter-Based Frameworks
|
||||
If you're using some other framework that is also filter-based, then you need to make sure that the Spring Security filters come first.
|
||||
This enables the `SecurityContextHolder` to be populated in time for use by the other filters.
|
||||
|
|
|
@ -238,7 +238,7 @@ public class FilterChainProxy extends GenericFilterBean {
|
|||
* @return matching filter list
|
||||
*/
|
||||
public List<Filter> getFilters(String url) {
|
||||
return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, null)
|
||||
return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, "GET")
|
||||
.getRequest())));
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.security.web.firewall;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Arrays;
|
||||
|
@ -35,6 +37,11 @@ import java.util.Set;
|
|||
* </p>
|
||||
* <ul>
|
||||
* <li>
|
||||
* Rejects HTTP methods that are not allowed. This specified to block
|
||||
* <a href="https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)">HTTP Verb tampering and XST attacks</a>.
|
||||
* See {@link #setAllowedHttpMethods(Collection)}
|
||||
* </li>
|
||||
* <li>
|
||||
* Rejects URLs that are not normalized to avoid bypassing security constraints. There is
|
||||
* no way to disable this as it is considered extremely risky to disable this constraint.
|
||||
* A few options to allow this behavior is to normalize the request prior to the firewall
|
||||
|
@ -66,6 +73,11 @@ import java.util.Set;
|
|||
* @since 4.2.4
|
||||
*/
|
||||
public class StrictHttpFirewall implements HttpFirewall {
|
||||
/**
|
||||
* Used to specify to {@link #setAllowedHttpMethods(Collection)} that any HTTP method should be allowed.
|
||||
*/
|
||||
private static final Set<String> ALLOW_ANY_HTTP_METHOD = Collections.unmodifiableSet(Collections.emptySet());
|
||||
|
||||
private static final String ENCODED_PERCENT = "%25";
|
||||
|
||||
private static final String PERCENT = "%";
|
||||
|
@ -82,6 +94,8 @@ public class StrictHttpFirewall implements HttpFirewall {
|
|||
|
||||
private Set<String> decodedUrlBlacklist = new HashSet<String>();
|
||||
|
||||
private Set<String> allowedHttpMethods = createDefaultAllowedHttpMethods();
|
||||
|
||||
public StrictHttpFirewall() {
|
||||
urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
|
||||
urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
|
||||
|
@ -92,6 +106,39 @@ public class StrictHttpFirewall implements HttpFirewall {
|
|||
this.decodedUrlBlacklist.add(PERCENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if any HTTP method is allowed. If this set to true, then no validation on the HTTP method will be performed.
|
||||
* This can open the application up to <a href="https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)">
|
||||
* HTTP Verb tampering and XST attacks</a>
|
||||
* @param unsafeAllowAnyHttpMethod if true, disables HTTP method validation, else resets back to the defaults. Default is false.
|
||||
* @see #setAllowedHttpMethods(Collection)
|
||||
* @since 5.1
|
||||
*/
|
||||
public void setUnsafeAllowAnyHttpMethod(boolean unsafeAllowAnyHttpMethod) {
|
||||
this.allowedHttpMethods = unsafeAllowAnyHttpMethod ? ALLOW_ANY_HTTP_METHOD : createDefaultAllowedHttpMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Determines which HTTP methods should be allowed. The default is to allow "DELETE", "GET", "HEAD", "OPTIONS",
|
||||
* "PATCH", "POST", and "PUT".
|
||||
* </p>
|
||||
*
|
||||
* @param allowedHttpMethods the case-sensitive collection of HTTP methods that are allowed.
|
||||
* @see #setUnsafeAllowAnyHttpMethod(boolean)
|
||||
* @since 5.1
|
||||
*/
|
||||
public void setAllowedHttpMethods(Collection<String> allowedHttpMethods) {
|
||||
if (allowedHttpMethods == null) {
|
||||
throw new IllegalArgumentException("allowedHttpMethods cannot be null");
|
||||
}
|
||||
if (allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
|
||||
this.allowedHttpMethods = ALLOW_ANY_HTTP_METHOD;
|
||||
} else {
|
||||
this.allowedHttpMethods = new HashSet<>(allowedHttpMethods);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Determines if semicolon is allowed in the URL (i.e. matrix variables). The default
|
||||
|
@ -242,6 +289,7 @@ public class StrictHttpFirewall implements HttpFirewall {
|
|||
|
||||
@Override
|
||||
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
|
||||
rejectForbiddenHttpMethod(request);
|
||||
rejectedBlacklistedUrls(request);
|
||||
|
||||
if (!isNormalized(request)) {
|
||||
|
@ -259,6 +307,18 @@ public class StrictHttpFirewall implements HttpFirewall {
|
|||
};
|
||||
}
|
||||
|
||||
private void rejectForbiddenHttpMethod(HttpServletRequest request) {
|
||||
if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
|
||||
return;
|
||||
}
|
||||
if (!this.allowedHttpMethods.contains(request.getMethod())) {
|
||||
throw new RequestRejectedException("The request was rejected because the HTTP method \"" +
|
||||
request.getMethod() +
|
||||
"\" was not included within the whitelist " +
|
||||
this.allowedHttpMethods);
|
||||
}
|
||||
}
|
||||
|
||||
private void rejectedBlacklistedUrls(HttpServletRequest request) {
|
||||
for (String forbidden : this.encodedUrlBlacklist) {
|
||||
if (encodedUrlContains(request, forbidden)) {
|
||||
|
@ -277,6 +337,18 @@ public class StrictHttpFirewall implements HttpFirewall {
|
|||
return new FirewalledResponse(response);
|
||||
}
|
||||
|
||||
private static Set<String> createDefaultAllowedHttpMethods() {
|
||||
Set<String> result = new HashSet<>();
|
||||
result.add(HttpMethod.DELETE.name());
|
||||
result.add(HttpMethod.GET.name());
|
||||
result.add(HttpMethod.HEAD.name());
|
||||
result.add(HttpMethod.OPTIONS.name());
|
||||
result.add(HttpMethod.PATCH.name());
|
||||
result.add(HttpMethod.POST.name());
|
||||
result.add(HttpMethod.PUT.name());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isNormalized(HttpServletRequest request) {
|
||||
if (!isNormalized(request.getRequestURI())) {
|
||||
return false;
|
||||
|
|
|
@ -69,7 +69,7 @@ public class FilterChainProxyTests {
|
|||
fcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher,
|
||||
Arrays.asList(filter)));
|
||||
fcp.setFilterChainValidator(mock(FilterChainProxy.FilterChainValidator.class));
|
||||
request = new MockHttpServletRequest();
|
||||
request = new MockHttpServletRequest("GET", "");
|
||||
request.setServletPath("/path");
|
||||
response = new MockHttpServletResponse();
|
||||
chain = mock(FilterChain.class);
|
||||
|
|
|
@ -16,11 +16,17 @@
|
|||
|
||||
package org.springframework.security.web.firewall;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
|
@ -31,12 +37,61 @@ public class StrictHttpFirewallTests {
|
|||
|
||||
private StrictHttpFirewall firewall = new StrictHttpFirewall();
|
||||
|
||||
private MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
private MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
|
||||
@Test
|
||||
public void getFirewalledRequestWhenInvalidMethodThenThrowsRequestRejectedException() {
|
||||
this.request.setMethod("INVALID");
|
||||
assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request))
|
||||
.isInstanceOf(RequestRejectedException.class);
|
||||
}
|
||||
|
||||
// blocks XST attacks
|
||||
@Test
|
||||
public void getFirewalledRequestWhenTraceMethodThenThrowsRequestRejectedException() {
|
||||
this.request.setMethod(HttpMethod.TRACE.name());
|
||||
assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request))
|
||||
.isInstanceOf(RequestRejectedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
// blocks XST attack if request is forwarded to a Microsoft IIS web server
|
||||
public void getFirewalledRequestWhenTrackMethodThenThrowsRequestRejectedException() {
|
||||
this.request.setMethod("TRACK");
|
||||
assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request))
|
||||
.isInstanceOf(RequestRejectedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
// HTTP methods are case sensitive
|
||||
public void getFirewalledRequestWhenLowercaseGetThenThrowsRequestRejectedException() {
|
||||
this.request.setMethod("get");
|
||||
assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request))
|
||||
.isInstanceOf(RequestRejectedException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFirewalledRequestWhenAllowedThenNoException() {
|
||||
List<String> allowedMethods = Arrays.asList("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT");
|
||||
for (String allowedMethod : allowedMethods) {
|
||||
this.request = new MockHttpServletRequest(allowedMethod, "");
|
||||
assertThatCode(() -> this.firewall.getFirewalledRequest(this.request))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFirewalledRequestWhenInvalidMethodAndAnyMethodThenNoException() {
|
||||
this.firewall.setUnsafeAllowAnyHttpMethod(true);
|
||||
this.request.setMethod("INVALID");
|
||||
assertThatCode(() -> this.firewall.getFirewalledRequest(this.request))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFirewalledRequestWhenRequestURINotNormalizedThenThrowsRequestRejectedException() throws Exception {
|
||||
for (String path : this.unnormalizedPaths) {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setRequestURI(path);
|
||||
try {
|
||||
this.firewall.getFirewalledRequest(this.request);
|
||||
|
@ -49,7 +104,7 @@ public class StrictHttpFirewallTests {
|
|||
@Test
|
||||
public void getFirewalledRequestWhenContextPathNotNormalizedThenThrowsRequestRejectedException() throws Exception {
|
||||
for (String path : this.unnormalizedPaths) {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setContextPath(path);
|
||||
try {
|
||||
this.firewall.getFirewalledRequest(this.request);
|
||||
|
@ -62,7 +117,7 @@ public class StrictHttpFirewallTests {
|
|||
@Test
|
||||
public void getFirewalledRequestWhenServletPathNotNormalizedThenThrowsRequestRejectedException() throws Exception {
|
||||
for (String path : this.unnormalizedPaths) {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setServletPath(path);
|
||||
try {
|
||||
this.firewall.getFirewalledRequest(this.request);
|
||||
|
@ -75,7 +130,7 @@ public class StrictHttpFirewallTests {
|
|||
@Test
|
||||
public void getFirewalledRequestWhenPathInfoNotNormalizedThenThrowsRequestRejectedException() throws Exception {
|
||||
for (String path : this.unnormalizedPaths) {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request = new MockHttpServletRequest("GET", "");
|
||||
this.request.setPathInfo(path);
|
||||
try {
|
||||
this.firewall.getFirewalledRequest(this.request);
|
||||
|
@ -352,7 +407,7 @@ public class StrictHttpFirewallTests {
|
|||
public void getFirewalledRequestWhenAllowUrlEncodedSlashAndLowercaseEncodedPathThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.firewall.setAllowSemicolon(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.setRequestURI("/context-root/a/b;%2f1/c");
|
||||
request.setContextPath("/context-root");
|
||||
request.setServletPath("");
|
||||
|
@ -365,7 +420,7 @@ public class StrictHttpFirewallTests {
|
|||
public void getFirewalledRequestWhenAllowUrlEncodedSlashAndUppercaseEncodedPathThenNoException() {
|
||||
this.firewall.setAllowUrlEncodedSlash(true);
|
||||
this.firewall.setAllowSemicolon(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
||||
request.setRequestURI("/context-root/a/b;%2F1/c");
|
||||
request.setContextPath("/context-root");
|
||||
request.setServletPath("");
|
||||
|
|
Loading…
Reference in New Issue