Skip to content

Reject redirect URIs containing pre-loaded OIDC response parameters#49959

Merged
mposolda merged 3 commits into
keycloak:mainfrom
jimmychakkalakal:fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI
Jun 18, 2026
Merged

Reject redirect URIs containing pre-loaded OIDC response parameters#49959
mposolda merged 3 commits into
keycloak:mainfrom
jimmychakkalakal:fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI

Conversation

@jimmychakkalakal

Copy link
Copy Markdown
Contributor

Prevent HTTP Parameter Pollution by validating that incoming redirect URIs do not already contain reserved OIDC query parameters (e.g., code, state, iss) before matching.

Closes #49430

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (windows-latest - temurin - 17)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (windows-latest - temurin - 21)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (ubuntu-latest - temurin - 21)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (ubuntu-latest - temurin - 17)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

@jimmychakkalakal jimmychakkalakal force-pushed the fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI branch from 1ca4f4f to 9efc7c9 Compare June 12, 2026 14:19
@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (windows-latest - temurin - 21)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (windows-latest - temurin - 17)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (ubuntu-latest - temurin - 17)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (ubuntu-latest - temurin - 21)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@jimmychakkalakal jimmychakkalakal force-pushed the fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI branch 2 times, most recently from eeb3fec to ca8502c Compare June 12, 2026 18:46

@keycloak-github-bot keycloak-github-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unreported flaky test detected, please review

@keycloak-github-bot

Copy link
Copy Markdown

Unreported flaky test detected

If the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR.

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (windows-latest - temurin - 17)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (windows-latest - temurin - 21)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (ubuntu-latest - temurin - 17)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

org.keycloak.testsuite.saml.SamlClientTest#testLoginWithOIDCClient

Keycloak CI - Java Distribution IT (ubuntu-latest - temurin - 21)

jakarta.ws.rs.ClientErrorException: HTTP 409 Conflict
	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:258)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:41)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:146)
	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104)
...

Report flaky test

@jimmychakkalakal jimmychakkalakal force-pushed the fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI branch from ca8502c to 621dc51 Compare June 15, 2026 08:47
@jimmychakkalakal jimmychakkalakal marked this pull request as ready for review June 15, 2026 10:00
@jimmychakkalakal jimmychakkalakal requested review from a team as code owners June 15, 2026 10:00

@mposolda mposolda left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimmychakkalakal Thanks for this PR!

Added some comments inline. Can you please check?

@gauravbsinghal

Copy link
Copy Markdown

Hi @jimmychakkalakal, I'm sharing this from Niro, a PR pentesting tool we're building to help OSS maintainers catch security regressions before merge. I ran it against this PR and it found the following issue.

A user initiating OIDC login can preload session_state in the redirect_uri, causing a valid code response with both user-chosen and Keycloak-issued session_state values; first-value parsers will track the wrong session state for that login.

Reproduction Steps

Precondition: use CR_003 (standard user in realm niro).

  1. Start authorization with a redirect URI that preloads session_state:
    GET http://host.docker.internal:8080/realms/niro/protocol/openid-connect/auth?client_id=niro-redirect-browser&response_type=code&scope=openid&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Fniro-redirect-callback%3Fsession_state%3Devil&state=abc
    Expected secure behavior: HTTP 400 Invalid parameter, matching the current behavior for redirect_uri values containing reserved response parameters such as ?code=evil or ?state=evil.
    Observed behavior: HTTP 200 with the Keycloak login form (title "Sign in to niro"), so the redirect_uri is accepted.
  2. Submit CR_003 to the login form action returned in step 1 (example from one run: POST /realms/niro/login-actions/authenticate?session_code=mXhMrii-d96QQUFGpeBUXclLyQ3cP5Q51kVzf_P4zE8&execution=0839fd5d-ff1b-43ba-8878-76e6c2233a0c&client_id=niro-redirect-browser&tab_id=lKA9JRLe42s&client_data=eyJydSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9uaXJvLXJlZGlyZWN0LWNhbGxiYWNrP3Nlc3Npb25fc3RhdGU9ZXZpbCIsInJ0IjoiY29kZSIsInN0IjoiYWJjIn0) with body username=<CR_003_IDENTIFIER>&password=<CR_003_SECRET>&credentialId= and the cookies from step 1.
    Observed vulnerable response: HTTP 302 with Location: http://127.0.0.1:8080/niro-redirect-callback?session_state=evil&state=abc&session_state=_Pv1DH91nl94UONvnNnviyfo&iss=http%3A%2F%2Fhost.docker.internal%3A8080%2Frealms%2Fniro&code=187c55b9-066a-527d-a15e-4938bb309530._Pv1DH91nl94UONvnNnviyfo.f883973a-8a45-488f-aff9-dedfcd9dcb32
    The client receives two session_state values in the authorization response.
  3. Repeat steps 1-2. I reproduced it twice more; both runs returned HTTP 302 to the callback with session_state_values=['evil', ''] and session_state_count=2.

Remediation Guidance

In services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java, add "session_state" to FORBIDDEN_OIDC_PARAMS so verifyRedirectUri rejects redirect URIs that preload Keycloak’s OIDC response parameter before auth begins. Extend RedirectUtilsTest with a case for https://example.com/callback?session_state=attack, alongside the existing code/state/iss coverage.

@jimmychakkalakal jimmychakkalakal force-pushed the fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI branch 3 times, most recently from 6fc2be0 to 4b30a25 Compare June 16, 2026 10:07
@mposolda mposolda self-assigned this Jun 16, 2026

@mposolda mposolda left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimmychakkalakal Thanks! The first 2 commits of your PR makes sense and I would approve such a PR.

But I don't understand why there is a need of the changes from your 3rd commit 4b30a25 . I don't see any relationship of this commit to the addressed GH issue. Did you added this commit by accident? Or is that any issue with adminEvents in the testsuite? In that case, it might be good to address it rather in the separate PR than this one.

@jimmychakkalakal

Copy link
Copy Markdown
Contributor Author

@jimmychakkalakal Thanks! The first 2 commits of your PR makes sense and I would approve such a PR.

But I don't understand why there is a need of the changes from your 3rd commit 4b30a25 . I don't see any relationship of this commit to the addressed GH issue. Did you added this commit by accident? Or is that any issue with adminEvents in the testsuite? In that case, it might be good to address it rather in the separate PR than this one.

Thanks @mposolda no, it wasn't an accident. The tests were failing, and I found out it has to do with uncleared admin events. I will then remove the adminevent releated changes from the PR

Prevent HTTP Parameter Pollution by validating that incoming redirect URIs do not already contain reserved OIDC query parameters (e.g., code, state, iss) before matching.

Closes keycloak#49430

Signed-off-by: Jimmy Chakkalakal <jimmy.chakkalakal@ibm.com>
- Replace hardcoded strings with OAuth2Constants and Constants
- Add missing parameters: session_state, response, kc_action, kc_action_status
- Update OIDCRedirectUriBuilder to use OAuth2Constants.RESPONSE
- Add tests for new parameters

Signed-off-by: Jimmy Chakkalakal <jimmy.chakkalakal@ibm.com>
@jimmychakkalakal jimmychakkalakal force-pushed the fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI branch from 4b30a25 to d613424 Compare June 17, 2026 14:33
Copilot AI review requested due to automatic review settings June 17, 2026 14:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds protection against HTTP parameter pollution in OIDC redirect URIs by rejecting redirect URIs that already contain reserved OIDC response parameters, and updates/extends tests accordingly.

Changes:

  • Added a forbidden-parameter check to RedirectUtils.verifyRedirectUri(...) to reject redirect URIs containing reserved OIDC/OAuth parameters in the query string.
  • Added new unit/integration tests for polluted redirect URIs and stabilized some admin-event assertions by clearing adminEvents before tests.
  • Replaced hard-coded "response" usage with OAuth2Constants.RESPONSE in OIDCRedirectUriBuilder.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java Removes a test that relied on now-forbidden session_state in the redirect URI.
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java Adds integration tests to ensure polluted redirect URIs are rejected; adds a positive test for custom query params.
tests/base/src/test/java/org/keycloak/tests/admin/realm/AbstractRealmRolesTest.java Clears adminEvents before each test to reduce cross-test interference.
tests/base/src/test/java/org/keycloak/tests/admin/identityprovider/IdentityProviderSamlTest.java Uses unique IDP alias per run to avoid collisions.
tests/base/src/test/java/org/keycloak/tests/admin/identityprovider/AbstractIdentityProviderTest.java Clears adminEvents before each test to reduce cross-test interference.
tests/base/src/test/java/org/keycloak/tests/admin/group/AbstractGroupTest.java Clears adminEvents before each test to reduce cross-test interference.
tests/base/src/test/java/org/keycloak/tests/admin/client/AbstractClientScopeTest.java Clears adminEvents before each test to reduce cross-test interference.
tests/base/src/test/java/org/keycloak/tests/admin/authentication/AbstractAuthenticationTest.java Clears adminEvents in setup to reduce cross-test interference.
tests/base/src/test/java/org/keycloak/tests/admin/AttackDetectionResourceTest.java Clears adminEvents mid-test before asserting subsequent admin events.
services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java Adds unit tests verifying polluted redirect URIs are rejected.
services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java Implements the forbidden-parameter rejection logic for redirect URIs.
services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java Uses OAuth2Constants.RESPONSE instead of hard-coded parameter name.

Comment thread services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java Outdated
Comment thread tests/base/src/test/java/org/keycloak/tests/admin/group/AbstractGroupTest.java Outdated
@jimmychakkalakal jimmychakkalakal force-pushed the fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI branch from d613424 to 076f43c Compare June 17, 2026 14:45
Copilot AI review requested due to automatic review settings June 17, 2026 15:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Signed-off-by: Jimmy Chakkalakal <jimmy.chakkalakal@ibm.com>
@jimmychakkalakal jimmychakkalakal force-pushed the fix/HTTP_Parameter_Pollution_in_OIDC_redirect_URI branch from c7b92e7 to e059fe6 Compare June 17, 2026 15:22
@jimmychakkalakal

Copy link
Copy Markdown
Contributor Author

@mposolda I have removed all admin event-related code from the tests. Unlike my previous commits, the pipeline is green this time. I assume it has something to do with the rebase.

Could you please review the PR? Thanks in advance!

@mposolda mposolda left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mposolda mposolda merged commit 18832bc into keycloak:main Jun 18, 2026
88 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CVE-2026-9689] HTTP Parameter Pollution in OIDC redirect URI allows response parameter duplication

4 participants