mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
Tighten UNSAFE_PATH_PATTERN against encoded path-traversal terminators
Fixes #48978 Extends the regex to cover encoded forms that previously bypassed detection: - %3B / %3b (encoded semicolon) - %09, %0A, %0D, %00 (control characters) - %252E (double-encoded dot) These encodings do not produce actual path traversal on conformant servers per RFC 3986 (percent-encoded characters are literals, not delimiters), but are semantically close enough to the patterns the regex was designed to block to warrant defense-in-depth coverage. The end-of-input anchor ($) is moved into the terminator class to collapse the two pattern alternatives into one, keeping the diff minimal. Test changes: - 8 new assertions covering encoded semicolons, control character terminators, and double-encoded dots. - 3 prior assertEquals flipped to assertNull (lines that previously asserted %252E%252E/, %252E%252E/#fragment, and ..%3Bsomething/ were allowed are now expected to be blocked). - 1 new negative test confirming %3B as legitimate path content (not following a parent-folder sequence) still resolves. Triple-encoded variants (e.g., %25252E) remain allowed; out of scope for this issue. Signed-off-by: Michał Kosiorek <michal.kosiorek@arklink.co>
This commit is contained in:
parent
ce12c7184c
commit
36b0b10dd2
2 changed files with 19 additions and 5 deletions
|
|
@ -150,8 +150,11 @@ public class RedirectUtils {
|
|||
}
|
||||
|
||||
// any access to parent folder /../ is unsafe with or without encoding
|
||||
// <sep> = / | %2F | %5C | \
|
||||
// <dots> = "..", including %2E and %252E (double-encoded) variants
|
||||
// <terminator> = / | %2F | %5C | \ | ; | %3B | %09 | %0A | %0D | %00 | end-of-input
|
||||
private final static Pattern UNSAFE_PATH_PATTERN = Pattern.compile(
|
||||
"(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|\\.){2}(/|%2[fF]|%5[cC]|\\\\|;)|(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|\\.){2}$");
|
||||
"(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|%252[eE]|\\.){2}(/|%2[fF]|%5[cC]|\\\\|;|%3[bB]|%09|%0[aAdD]|%00|$)");
|
||||
|
||||
private static boolean areWildcardsAllowed(URI redirectUri) {
|
||||
// wildcars are only allowed if no user-info and no unsafe pattern in path
|
||||
|
|
|
|||
|
|
@ -267,15 +267,26 @@ public class RedirectUtilsTest {
|
|||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test%5C..", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test%5C..;/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/%2F%2E%2E%2Fdocumentation", set, false));
|
||||
|
||||
// Issue #48978 — encoded terminators that previously bypassed the regex.
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%3B/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%3b/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%09/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%0A/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%0D/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%00/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%3Bsomething/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/%252E%252E/", set, false)); // double-encoded dots
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/%252E%252E/#encodeTest=a%3Cb", set, false));
|
||||
|
||||
Assert.assertEquals("https://keycloak.org/test/.../", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/.../", set, false));
|
||||
Assert.assertEquals("https://keycloak.org/test/%2E../", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/%2E../", set, false)); // encoded
|
||||
Assert.assertEquals("https://keycloak.org/test/some%2Fthing/", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/some%2Fthing/", set, false)); // encoded
|
||||
Assert.assertEquals("https://keycloak.org/test/./", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/./", set, false));
|
||||
Assert.assertEquals("https://keycloak.org/test/%252E%252E/", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/%252E%252E/", set, false)); // double-encoded
|
||||
Assert.assertEquals("https://keycloak.org/test/%252E%252E/#encodeTest=a%3Cb", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/%252E%252E/#encodeTest=a%3Cb", set, false)); // double-encoded
|
||||
Assert.assertEquals("https://keycloak.org/test/%25252E%25252E/", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/%25252E%25252E/", set, false)); // triple-encoded
|
||||
Assert.assertEquals("https://keycloak.org/test/%25252E%25252E/", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/%25252E%25252E/", set, false)); // triple-encoded; out of scope for #48978
|
||||
Assert.assertEquals("https://keycloak.org/exact/%5C%2F/..", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/exact/%5C%2F/..", set, false));
|
||||
Assert.assertEquals("https://keycloak.org/test/..%3Bsomething/", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%3Bsomething/", set, false));
|
||||
// Negative test: %3B as legitimate path content (not following `..`) must still resolve.
|
||||
Assert.assertEquals("https://keycloak.org/test/file%3Bname/", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/file%3Bname/", set, false));
|
||||
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak%2Eorg/test/", set, false));
|
||||
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org%2Ftest%2F%40sample.com", set, false));
|
||||
|
|
|
|||
Loading…
Reference in a new issue