High severityNVD Advisory· Published Mar 23, 2021· Updated Aug 3, 2024
CVE-2021-20222
CVE-2021-20222
Description
A flaw was found in keycloak. The new account console in keycloak can allow malicious code to be executed using the referrer URL. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.keycloak:keycloak-parentMaven | >= 9.0.0, < 12.0.3 | 12.0.3 |
Affected products
1Patches
13b80eee5bfdfKEYCLOAK-17033: Reflected XSS attack with referrer in new account
2 files changed · +39 −13
testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/ReferrerTest.java+38 −12 modified@@ -37,6 +37,8 @@ public class ReferrerTest extends AbstractAccountTest { public static final String FAKE_CLIENT_ID = "fake-client-name"; public static final String REFERRER_LINK_TEXT = "Back to " + LOCALE_CLIENT_NAME_LOCALIZED; + public static final String FAKE_CLIENT_URL_CONTEXT = "auth/non-existing-page/"; + public static final String FAKE_CLIENT_URL_FRAGMENT = "?foo=bar&bar=foo#anchor"; @Page private WelcomeScreen welcomeScreen; @@ -52,19 +54,36 @@ public void addTestRealms(List<RealmRepresentation> testRealms) { ClientRepresentation testClient = new ClientRepresentation(); testClient.setClientId(FAKE_CLIENT_ID); testClient.setName(LOCALE_CLIENT_NAME); - testClient.setRedirectUris(Collections.singletonList(getFakeClientUrl())); + + // Redirect URIs are no longer allowed to contain a fragment, so we + // need the wildcard in order to use fragments in tests + testClient.setRedirectUris(Collections.singletonList(getFakeClientUrl("*"))); + testClient.setEnabled(true); testRealm.setClients(Collections.singletonList(testClient)); testRealm.setAccountTheme(LOCALIZED_THEME_PREVIEW); // using localized custom theme for the fake client localized name } + @Test + // https://issues.redhat.com/browse/KEYCLOAK-17033 + // If the referrer is unescaped, this test will throw an exception. + // org.openqa.selenium.UnhandledAlertException: unexpected alert open: {Alert text : XSS} + public void reflectedXSSTest() { + String attackUrl = getFakeClientUrl("'+alert('XSS')+'"); + welcomeScreen.navigateTo(FAKE_CLIENT_ID, attackUrl); + + welcomeScreen.header().clickLoginBtn(); + loginToAccount(); + welcomeScreen.clickPersonalInfoLink(); + } + @Test public void loggedInWelcomeScreenTest() { welcomeScreen.header().clickLoginBtn(); loginToAccount(); - welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl()); + welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl(FAKE_CLIENT_URL_FRAGMENT)); welcomeScreen.header().assertLoginBtnVisible(false); welcomeScreen.header().assertLogoutBtnVisible(true); @@ -73,7 +92,7 @@ public void loggedInWelcomeScreenTest() { @Test public void loggedOutWelcomeScreenTest() { - welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl()); + welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl(FAKE_CLIENT_URL_FRAGMENT)); welcomeScreen.header().assertLoginBtnVisible(true); welcomeScreen.header().assertLogoutBtnVisible(false); @@ -85,15 +104,15 @@ public void loggedInPageTest() { welcomeScreen.header().clickLoginBtn(); loginToAccount(); - welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl()); + welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl(FAKE_CLIENT_URL_FRAGMENT)); welcomeScreen.clickPersonalInfoLink(); testReferrer(personalInfoPage.header(), true); } @Test public void loggedOutPageTest() { - welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl()); + welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl(FAKE_CLIENT_URL_FRAGMENT)); welcomeScreen.clickPersonalInfoLink(); loginToAccount(); @@ -102,21 +121,21 @@ public void loggedOutPageTest() { @Test public void badClientNameTest() { - welcomeScreen.navigateTo(FAKE_CLIENT_ID + "-bad", getFakeClientUrl()); + welcomeScreen.navigateTo(FAKE_CLIENT_ID + "-bad", getFakeClientUrl(FAKE_CLIENT_URL_FRAGMENT)); testReferrer(welcomeScreen.header(), false); - welcomeScreen.navigateTo(FAKE_CLIENT_ID + "-bad", getFakeClientUrl()); + welcomeScreen.navigateTo(FAKE_CLIENT_ID + "-bad", getFakeClientUrl(FAKE_CLIENT_URL_FRAGMENT)); welcomeScreen.clickPersonalInfoLink(); loginToAccount(); testReferrer(personalInfoPage.header(), false); } @Test public void badClientUriTest() { - welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl() + "-bad"); + welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrlWithBadContext()); testReferrer(welcomeScreen.header(), false); - welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl() + "-bad"); + welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrlWithBadContext()); welcomeScreen.clickPersonalInfoLink(); loginToAccount(); testReferrer(personalInfoPage.header(), false); @@ -126,17 +145,24 @@ private void testReferrer(AbstractHeader header, boolean expectReferrerVisible) if (expectReferrerVisible) { assertEquals(REFERRER_LINK_TEXT, header.getReferrerLinkText()); header.clickReferrerLink(); - assertCurrentUrlEquals(getFakeClientUrl()); + assertCurrentUrlEquals(getFakeClientUrl(FAKE_CLIENT_URL_FRAGMENT)); } else { header.assertReferrerLinkVisible(false); } } - private String getFakeClientUrl() { + private String getFakeClientUrl(String suffix) { + // we need to use some page which host exists – Firefox is throwing exceptions like crazy if we try to load + // a page on a non-existing host, like e.g. http://non-existing-server/ + // also we need to do this here as getAuthServerRoot is not ready when firing this class' constructor + return getAuthServerRoot() + FAKE_CLIENT_URL_CONTEXT + suffix; + } + + private String getFakeClientUrlWithBadContext() { // we need to use some page which host exists – Firefox is throwing exceptions like crazy if we try to load // a page on a non-existing host, like e.g. http://non-existing-server/ // also we need to do this here as getAuthServerRoot is not ready when firing this class' constructor - return getAuthServerRoot() + "auth/non-existing-page/?foo=bar&bar=foo#anchor"; + return getAuthServerRoot() + "bad/" + FAKE_CLIENT_URL_CONTEXT; } }
themes/src/main/resources/theme/keycloak.v2/account/index.ftl+1 −1 modified@@ -57,7 +57,7 @@ <#if referrer??> var referrer = '${referrer}'; var referrerName = '${referrerName}'; - var referrerUri = '${referrer_uri?no_esc}'; + var referrerUri = '${referrer_uri}'.replace('&', '&'); </#if> <#if msg??>
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-2mq8-99q7-55wxghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-20222ghsaADVISORY
- access.redhat.com/security/cve/cve-2021-20222ghsaWEB
- bugzilla.redhat.com/show_bug.cgighsax_refsource_MISCWEB
- github.com/keycloak/keycloak/commit/3b80eee5bfdf2b80c47465c0f2eaf70074808741ghsaWEB
News mentions
0No linked articles in our index yet.