8000 UNSAFE_PATH_PATTERN regex to cover percent-encoded terminators and control characters · Issue #48978 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
8000

UNSAFE_PATH_PATTERN regex to cover percent-encoded terminators and control characters #48978

Description

@abstractj

Description

The UNSAFE_PATH_PATTERN regex in RedirectUtils.java (line 153) catches literal path traversal sequences (../, ..;/) and their percent-encoded dot variants (%2E%2E), but does not cover percent-encoded terminators or control characters in the path. Since the check runs against URI.getRawPath(), the following encoded forms pass through unmatched:

  • %3B / %3b (encoded semicolon)
  • %0A, %0D, %09, %00 (control characters)
  • %252E%252E (double-encoded dots)

While none of these cause actual path traversal on standard web servers (percent-encoded characters are treated as literals per RFC 3986, not as delimiters), tightening the regex provides defense-in-depth against non-conformant downstream stacks that may double-decode or treat encoded semicolons as path parameter separators.

Current regex

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}$");

The terminator group (/|%2[fF]|%5[cC]|\\\\|;) accepts literal ; but not %3B, %3b, or control-character bytes.

Suggested fix

Option A — extend the terminator character class to include %3[bB] and reject control characters:

private final static Pattern UNSAFE_PATH_PATTERN = Pattern.compile(
    "(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|\\.){2}(/|%2[fF]|%5[cC]|\\\\|;|%3[bB]|%0[aAdD09]|%00)" +
    "|(/|%2[fF]|%5[cC]|\\\\)(%2[eE]|\\.){2}$");

Option B — replace regex-only validation with canonical path normalization:

private static boolean areWildcardsAllowed(URI redirectUri) {
    if (redirectUri.getRawUserInfo() != null) return false;
    String raw = redirectUri.getRawPath();
    if (raw == null) return true;
    // Reject control characters in raw path
    for (int i = 0; i < raw.length(); i++) {
        char c = raw.charAt(i);
        if (c < 0x20 || c == 0x7F) return false;
    }
    // Decode once and check for traversal in decoded form
    String decoded = redirectUri.getPath();
    if (decoded != null && decoded.contains("..")) return false;
    return !UNSAFE_PATH_PATTERN.matcher(raw).find();
}

Option B is more robust against future encoding variants but changes more code.

Affected file

services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java

Additional test cases

The following should be added to RedirectUtilsTest.testEncodedRedirectUri():

// Encoded semicolon variants
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));

// Control character terminators
Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/test/..%0A/", 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/..%00/", set, false));

---
*This issue was originally tracked in the private repository. Migrated by @abstractj.*

Activity

  1. added
    area/oidcOpenID Connect / OAuth 2.0 — tokens, endpoints, scopes, DCR, PAR, DPoP
    kind/bugCategorizes a PR related to a bug
    on May 13, 2026
  2. added theissue type on May 13, 2026
  3. changed the issue type fromtoon May 19, 2026
  4. added 2 commits that reference this issue on May 21, 2026
    62a5d23
    7b26bd2
  5. added a commit that references this issue on May 21, 2026
    c3f34c3
  6. 29 remaining items

  7. added a commit that references this issue on Jul 20, 2026
    87ddd2c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Fields

    No fields configured for weakness.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0