|
| 1 | +[[webflux-serverwebexchangefirewall]] |
| 2 | += ServerWebExchangeFirewall |
| 3 | + |
| 4 | +There are various ways a request can be created by malicious users that can exploit applications. |
| 5 | +Spring Security provides the `ServerWebExchangeFirewall` to allow rejecting requests that look malicious. |
| 6 | +The default implementation is `StrictServerWebExchangeFirewall` which rejects malicious requests. |
| 7 | + |
| 8 | +For example a request could contain path-traversal sequences (such as `/../`) or multiple forward slashes (`//`) that could also cause pattern-matches to fail. |
| 9 | +Some containers normalize these out before performing the servlet mapping, but others do not. |
| 10 | +To protect against issues like these, `WebFilterChainProxy` uses a `ServerWebExchangeFirewall` strategy to check and wrap the request. |
| 11 | +By default, un-normalized requests are automatically rejected, and path parameters are removed for matching purposes. |
| 12 | +(So, for example, an original request path of `/secure;hack=1/somefile.html;hack=2` is returned as `/secure/somefile.html`.) |
| 13 | +It is, therefore, essential that a `WebFilterChainProxy` is used. |
| 14 | + |
| 15 | +In practice, we recommend that you use method security at your service layer, to control access to your application, rather than rely entirely on the use of security constraints defined at the web-application level. |
| 16 | +URLs change, and it is difficult to take into account all the possible URLs that an application might support and how requests might be manipulated. |
| 17 | +You should restrict yourself to using a few simple patterns that are simple to understand. |
| 18 | +Always try to use a "`deny-by-default`" approach, where you have a catch-all wildcard (`/**` or `**`) defined last to deny access. |
| 19 | + |
| 20 | +Security defined at the service layer is much more robust and harder to bypass, so you should always take advantage of Spring Security's method security options. |
| 21 | + |
| 22 | +You can customize the `ServerWebExchangeFirewall` by exposing it as a Bean. |
| 23 | + |
| 24 | +.Allow Matrix Variables |
| 25 | +[tabs] |
| 26 | +====== |
| 27 | +Java:: |
| 28 | ++ |
| 29 | +[source,java,role="primary"] |
| 30 | +---- |
| 31 | +@Bean |
| 32 | +public StrictServerWebExchangeFirewall httpFirewall() { |
| 33 | + StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall(); |
| 34 | + firewall.setAllowSemicolon(true); |
| 35 | + return firewall; |
| 36 | +} |
| 37 | +---- |
| 38 | +
|
| 39 | +Kotlin:: |
| 40 | ++ |
| 41 | +[source,kotlin,role="secondary"] |
| 42 | +---- |
| 43 | +@Bean |
| 44 | +fun httpFirewall(): StrictServerWebExchangeFirewall { |
| 45 | + val firewall = StrictServerWebExchangeFirewall() |
| 46 | + firewall.setAllowSemicolon(true) |
| 47 | + return firewall |
| 48 | +} |
| 49 | +---- |
| 50 | +====== |
| 51 | + |
| 52 | +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 `StrictServerWebExchangeFirewall` provides an allowed list of valid HTTP methods that are allowed. |
| 53 | +The default valid methods are `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, and `PUT`. |
| 54 | +If your application needs to modify the valid methods, you can configure a custom `StrictServerWebExchangeFirewall` bean. |
| 55 | +The following example allows only HTTP `GET` and `POST` methods: |
| 56 | + |
| 57 | + |
| 58 | +.Allow Only GET & POST |
| 59 | +[tabs] |
| 60 | +====== |
| 61 | +Java:: |
| 62 | ++ |
| 63 | +[source,java,role="primary"] |
| 64 | +---- |
| 65 | +@Bean |
| 66 | +public StrictServerWebExchangeFirewall httpFirewall() { |
| 67 | + StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall(); |
| 68 | + firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST")); |
| 69 | + return firewall; |
| 70 | +} |
| 71 | +---- |
| 72 | +
|
| 73 | +Kotlin:: |
| 74 | ++ |
| 75 | +[source,kotlin,role="secondary"] |
| 76 | +---- |
| 77 | +@Bean |
| 78 | +fun httpFirewall(): StrictServerWebExchangeFirewall { |
| 79 | + val firewall = StrictServerWebExchangeFirewall() |
| 80 | + firewall.setAllowedHttpMethods(listOf("GET", "POST")) |
| 81 | + return firewall |
| 82 | +} |
| 83 | +---- |
| 84 | +====== |
| 85 | + |
| 86 | +If you must allow any HTTP method (not recommended), you can use `StrictServerWebExchangeFirewall.setUnsafeAllowAnyHttpMethod(true)`. |
| 87 | +Doing so entirely disables validation of the HTTP method. |
| 88 | + |
| 89 | + |
| 90 | +[[webflux-serverwebexchangefirewall-headers-parameters]] |
| 91 | +`StrictServerWebExchangeFirewall` also checks header names and values and parameter names. |
| 92 | +It requires that each character have a defined code point and not be a control character. |
| 93 | + |
| 94 | +This requirement can be relaxed or adjusted as necessary by using the following methods: |
| 95 | + |
| 96 | +* `StrictServerWebExchangeFirewall#setAllowedHeaderNames(Predicate)` |
| 97 | +* `StrictServerWebExchangeFirewall#setAllowedHeaderValues(Predicate)` |
| 98 | +* `StrictServerWebExchangeFirewall#setAllowedParameterNames(Predicate)` |
| 99 | + |
| 100 | +[NOTE] |
| 101 | +==== |
| 102 | +Parameter values can be also controlled with `setAllowedParameterValues(Predicate)`. |
| 103 | +==== |
| 104 | + |
| 105 | +For example, to switch off this check, you can wire your `StrictServerWebExchangeFirewall` with `Predicate` instances that always return `true`: |
| 106 | + |
| 107 | +.Allow Any Header Name, Header Value, and Parameter Name |
| 108 | +[tabs] |
| 109 | +====== |
| 110 | +Java:: |
| 111 | ++ |
| 112 | +[source,java,role="primary"] |
| 113 | +---- |
| 114 | +@Bean |
| 115 | +public StrictServerWebExchangeFirewall httpFirewall() { |
| 116 | + StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall(); |
| 117 | + firewall.setAllowedHeaderNames((header) -> true); |
| 118 | + firewall.setAllowedHeaderValues((header) -> true); |
| 119 | + firewall.setAllowedParameterNames((parameter) -> true); |
| 120 | + return firewall; |
| 121 | +} |
| 122 | +---- |
| 123 | +
|
| 124 | +Kotlin:: |
| 125 | ++ |
| 126 | +[source,kotlin,role="secondary"] |
| 127 | +---- |
| 128 | +@Bean |
| 129 | +fun httpFirewall(): StrictServerWebExchangeFirewall { |
| 130 | + val firewall = StrictServerWebExchangeFirewall() |
| 131 | + firewall.setAllowedHeaderNames { true } |
| 132 | + firewall.setAllowedHeaderValues { true } |
| 133 | + firewall.setAllowedParameterNames { true } |
| 134 | + return firewall |
| 135 | +} |
| 136 | +---- |
| 137 | +====== |
| 138 | + |
| 139 | +Alternatively, there might be a specific value that you need to allow. |
| 140 | + |
| 141 | +For example, iPhone Xʀ uses a `User-Agent` that includes a character that is not in the ISO-8859-1 charset. |
| 142 | +Due to this fact, some application servers parse this value into two separate characters, the latter being an undefined character. |
| 143 | + |
| 144 | +You can address this with the `setAllowedHeaderValues` method: |
| 145 | + |
| 146 | +.Allow Certain User Agents |
| 147 | +[tabs] |
| 148 | +====== |
| 149 | +Java:: |
| 150 | ++ |
| 151 | +[source,java,role="primary"] |
| 152 | +---- |
| 153 | +@Bean |
| 154 | +public StrictServerWebExchangeFirewall httpFirewall() { |
| 155 | + StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall(); |
| 156 | + Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*"); |
| 157 | + Pattern userAgent = ...; |
| 158 | + firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches()); |
| 159 | + return firewall; |
| 160 | +} |
| 161 | +---- |
| 162 | +
|
| 163 | +Kotlin:: |
| 164 | ++ |
| 165 | +[source,kotlin,role="secondary"] |
| 166 | +---- |
| 167 | +@Bean |
| 168 | +fun httpFirewall(): StrictServerWebExchangeFirewall { |
| 169 | + val firewall = StrictServerWebExchangeFirewall() |
| 170 | + val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*") |
| 171 | + val userAgent = Pattern.compile(...) |
| 172 | + firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() } |
| 173 | + return firewall |
| 174 | +} |
| 175 | +---- |
| 176 | +====== |
| 177 | + |
| 178 | +In the case of header values, you may instead consider parsing them as UTF-8 at verification time: |
| 179 | + |
| 180 | +.Parse Headers As UTF-8 |
| 181 | +[tabs] |
| 182 | +====== |
| 183 | +Java:: |
| 184 | ++ |
| 185 | +[source,java,role="primary"] |
| 186 | +---- |
| 187 | +firewall.setAllowedHeaderValues((header) -> { |
| 188 | + String parsed = new String(header.getBytes(ISO_8859_1), UTF_8); |
| 189 | + return allowed.matcher(parsed).matches(); |
| 190 | +}); |
| 191 | +---- |
| 192 | +
|
| 193 | +Kotlin:: |
| 194 | ++ |
| 195 | +[source,kotlin,role="secondary"] |
| 196 | +---- |
| 197 | +firewall.setAllowedHeaderValues { |
| 198 | + val parsed = String(header.getBytes(ISO_8859_1), UTF_8) |
| 199 | + return allowed.matcher(parsed).matches() |
| 200 | +} |
| 201 | +---- |
| 202 | +====== |
0 commit comments