|
25 | 25 | import java.util.Collections; |
26 | 26 | import java.util.Iterator; |
27 | 27 | import java.util.function.Predicate; |
| 28 | +import java.util.regex.Matcher; |
| 29 | +import java.util.regex.Pattern; |
28 | 30 |
|
29 | 31 | import javax.servlet.http.HttpServletResponse; |
30 | 32 |
|
@@ -196,6 +198,106 @@ public void testNoNonceMimeMatcher() { |
196 | 198 | Assert.assertEquals("/foo/images/home.jpg", response.encodeURL("/foo/images/home.jpg")); |
197 | 199 | } |
198 | 200 |
|
| 201 | + @Test |
| 202 | + public void testMultipleTokens() { |
| 203 | + String nonce = "TESTNONCE"; |
| 204 | + String testURL = "/foo/bar?" + Constants.CSRF_NONCE_SESSION_ATTR_NAME + "=sample"; |
| 205 | + CsrfPreventionFilter.CsrfResponseWrapper response = new CsrfPreventionFilter.CsrfResponseWrapper(new NonEncodingResponse(), |
| 206 | + Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonce, null); |
| 207 | + |
| 208 | + Assert.assertTrue("Original URL does not contain CSRF token", |
| 209 | + testURL.contains(Constants.CSRF_NONCE_SESSION_ATTR_NAME)); |
| 210 | + |
| 211 | + String result = response.encodeURL(testURL); |
| 212 | + |
| 213 | + int pos = result.indexOf(Constants.CSRF_NONCE_SESSION_ATTR_NAME); |
| 214 | + Assert.assertTrue("Result URL does not contain CSRF token", |
| 215 | + pos >= 0); |
| 216 | + pos = result.indexOf(Constants.CSRF_NONCE_SESSION_ATTR_NAME, pos + 1); |
| 217 | + Assert.assertFalse("Result URL contains multiple CSRF tokens: " + result, |
| 218 | + pos >= 0); |
| 219 | + } |
| 220 | + |
| 221 | + @Test |
| 222 | + public void testURLCleansing() { |
| 223 | + String[] urls = new String[] { |
| 224 | + "/foo/bar", |
| 225 | + "/foo/bar?", |
| 226 | + "/foo/bar?csrf", |
| 227 | + "/foo/bar?csrf&", |
| 228 | + "/foo/bar?csrf=", |
| 229 | + "/foo/bar?csrf=&", |
| 230 | + "/foo/bar?csrf=abc", |
| 231 | + "/foo/bar?csrf=abc&bar=foo", |
| 232 | + "/foo/bar?bar=foo&csrf=abc", |
| 233 | + "/foo/bar?bar=foo&csrf=abc&foo=bar", |
| 234 | + "/foo/bar?csrfx=foil&bar=foo&csrf=abc&foo=bar", |
| 235 | + "/foo/bar?csrfx=foil&bar=foo&csrf=abc&foo=bar&csrf=def", |
| 236 | + "/foo/bar?csrf=&csrf&csrf&csrf&csrf=abc&csrf=", |
| 237 | + "/foo/bar?xcsrf=&xcsrf&xcsrf&xcsrf&xcsrf=abc&xcsrf=", |
| 238 | + "/foo/bar?xcsrf=&xcsrf&xcsrf&csrf=foo&xcsrf&xcsrf=abc&csrf=bar&xcsrf=&", |
| 239 | + "/foo/bar?bar=fo?&csrf=abc", |
| 240 | + "/foo/bar?bar=fo?&csrf=abc&baz=boh", |
| 241 | + }; |
| 242 | + |
| 243 | + String csrfParameterName = "csrf"; |
| 244 | + |
| 245 | + for(String url : urls) { |
| 246 | + String result = CsrfPreventionFilter.CsrfResponseWrapper.removeQueryParameters(url, csrfParameterName); |
| 247 | + |
| 248 | + Assert.assertEquals("Failed to cleanse URL '" + url + "' properly", dumbButAccurateCleanse(url, csrfParameterName), result); |
| 249 | + } |
| 250 | + |
| 251 | + } |
| 252 | + |
| 253 | + private static String dumbButAccurateCleanse(String url, String csrfParameterName) { |
| 254 | + // Get rid of [&csrf=value] with optional =value |
| 255 | + Pattern p = Pattern.compile(Pattern.quote("&") + Pattern.quote(csrfParameterName) + "(=[^&]*)?(&|$)"); |
| 256 | + |
| 257 | + // Do this iteratively to get everything |
| 258 | + Matcher m = p.matcher(url); |
| 259 | + |
| 260 | + while (m.find()) { |
| 261 | + url = m.replaceFirst("$2"); |
| 262 | + m = p.matcher(url); |
| 263 | + } |
| 264 | + |
| 265 | + // Get rid of [?csrf=value] with optional =value |
| 266 | + url = url.replaceAll(Pattern.quote("?") + csrfParameterName + "(=[^&]*)?(&|$)", "?"); |
| 267 | + |
| 268 | + if (url.endsWith("?")) { |
| 269 | + // Special case: it's possible to have multiple ? in a URL: |
| 270 | + // the query-string is technically allowed to contain ? characters. |
| 271 | + // Only trim the trailing ? if it is actually the quest-string |
| 272 | + // separator. |
| 273 | + if(url.lastIndexOf('?', url.length() - 2) < 0) { |
| 274 | + url = url.substring(0, url.length() - 1); |
| 275 | + } |
| 276 | + } |
| 277 | + |
| 278 | + return url; |
| 279 | + } |
| 280 | + |
| 281 | + @Test |
| 282 | + public void testDuplicateTokens() { |
| 283 | + String nonce = "TESTNONCE"; |
| 284 | + String testURL = "/foo/bar?" + Constants.CSRF_NONCE_SESSION_ATTR_NAME + "=" + nonce; |
| 285 | + CsrfPreventionFilter.CsrfResponseWrapper response = new CsrfPreventionFilter.CsrfResponseWrapper(new NonEncodingResponse(), |
| 286 | + Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonce, null); |
| 287 | + |
| 288 | + Assert.assertTrue("Original URL does not contain CSRF token", |
| 289 | + testURL.contains(Constants.CSRF_NONCE_SESSION_ATTR_NAME)); |
| 290 | + |
| 291 | + String result = response.encodeURL(testURL); |
| 292 | + |
| 293 | + int pos = result.indexOf(Constants.CSRF_NONCE_SESSION_ATTR_NAME); |
| 294 | + Assert.assertTrue("Result URL does not contain CSRF token", |
| 295 | + pos >= 0); |
| 296 | + pos = result.indexOf(Constants.CSRF_NONCE_SESSION_ATTR_NAME, pos + 1); |
| 297 | + Assert.assertFalse("Result URL contains multiple CSRF tokens: " + result, |
| 298 | + pos >= 0); |
| 299 | + } |
| 300 | + |
199 | 301 | private static class MimeTypeServletContext extends TesterServletContext { |
200 | 302 | private String mimeType; |
201 | 303 |
|
|
0 commit comments