CVE-2023-0105
Description
A flaw was found in Keycloak. This flaw allows impersonation and lockout due to the email trust not being handled correctly in Keycloak. An attacker can shadow other users with the same email and lockout or impersonate them.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Keycloak fails to reset the emailVerified flag on email change, enabling impersonation or lockout of other users with the same email.
Root
Cause The vulnerability stems from Keycloak's failure to reset the emailVerified flag when a user's email address is modified, particularly during identity provider synchronization. When an email is changed, the trust status (verified) is not invalidated, allowing the new email to inherit the verified state [1][4].
Exploitation
An attacker can exploit this by changing their own email address to match that of another user, for example through an identity provider that forces attribute synchronization. If the target user's email was previously verified, the attacker's account may inherit that verified status, enabling them to impersonate the target or lock them out by taking over the email identity [2][4].
Impact
Successful exploitation allows an attacker to impersonate the victim, gaining access to resources or performing actions on their behalf. Alternatively, the attacker could lock out the victim by claiming their email, preventing the legitimate user from logging in [1][4].
Mitigation
The issue is fixed in Keycloak version 22.0.1. The fix ensures that emailVerified is set to false whenever an email is changed, requiring re-verification [2][4]. Users should upgrade to the latest version to protect against this vulnerability.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.keycloak:keycloak-coreMaven | < 22.0.1 | 22.0.1 |
Affected products
2- redhat.com/Keycloakv5Range: n/a
Patches
187a50d3ba790Revert emailVerified to false if email modified on force-sync non-trusted broker
2 files changed · +50 −8
services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java+13 −1 modified@@ -1018,7 +1018,7 @@ private void updateFederatedIdentity(BrokeredIdentityContext context, UserModel } private void setBasicUserAttributes(BrokeredIdentityContext context, UserModel federatedUser) { - setDiffAttrToConsumer(federatedUser.getEmail(), context.getEmail(), federatedUser::setEmail); + setDiffAttrToConsumer(federatedUser.getEmail(), context.getEmail(), email -> setEmail(context, federatedUser, email)); setDiffAttrToConsumer(federatedUser.getFirstName(), context.getFirstName(), federatedUser::setFirstName); setDiffAttrToConsumer(federatedUser.getLastName(), context.getLastName(), federatedUser::setLastName); } @@ -1030,6 +1030,18 @@ private void setDiffAttrToConsumer(String actualValue, String newValue, Consumer } } + private void setEmail(BrokeredIdentityContext context, UserModel federatedUser, String newEmail) { + federatedUser.setEmail(newEmail); + // change email verified depending if it is trusted or not + if (context.getIdpConfig().isTrustEmail() && !Boolean.parseBoolean(context.getAuthenticationSession().getAuthNote(AbstractIdpAuthenticator.UPDATE_PROFILE_EMAIL_CHANGED))) { + logger.tracef("Email verified automatically after updating user '%s' through Identity provider '%s' ", federatedUser.getUsername(), context.getIdpConfig().getAlias()); + federatedUser.setEmailVerified(true); + } else { + logger.tracef("Email verified reset to false after updating user '%s' through Identity provider '%s' ", federatedUser.getUsername(), context.getIdpConfig().getAlias()); + federatedUser.setEmailVerified(false); + } + } + private void migrateFederatedIdentityId(BrokeredIdentityContext context, UserModel federatedUser) { FederatedIdentityModel identityModel = this.session.users().getFederatedIdentity(this.realmModel, federatedUser, context.getIdpConfig().getAlias()); FederatedIdentityModel migratedIdentityModel = new FederatedIdentityModel(identityModel, context.getId());
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerTest.java+37 −7 modified@@ -504,12 +504,22 @@ public void testIdPNotFound() { @Test public void testIdPForceSyncUserAttributes() { - checkUpdatedUserAttributesIdP(true); + checkUpdatedUserAttributesIdP(true, false); + } + + @Test + public void testIdPForceSyncTrustEmailUserAttributes() { + checkUpdatedUserAttributesIdP(true, true); } @Test public void testIdPNotForceSyncUserAttributes() { - checkUpdatedUserAttributesIdP(false); + checkUpdatedUserAttributesIdP(false, false); + } + + @Test + public void testIdPNotForceSyncTrustEmailUserAttributes() { + checkUpdatedUserAttributesIdP(false, true); } @Test @@ -593,7 +603,7 @@ private void updateIdPClaimFilter(IdentityProviderRepresentation idProvider, Ide assertThat("Claim value didn't change", idProvider.getConfig().get(IdentityProviderModel.CLAIM_FILTER_VALUE), Matchers.equalTo(claimFilterValue)); } - private void checkUpdatedUserAttributesIdP(boolean isForceSync) { + private void checkUpdatedUserAttributesIdP(boolean isForceSync, boolean isTrustEmail) { final String IDP_NAME = getBrokerConfiguration().getIDPAlias(); final String USERNAME = "demo-user"; final String PASSWORD = "demo-pwd"; @@ -612,16 +622,18 @@ private void checkUpdatedUserAttributesIdP(boolean isForceSync) { UsersResource providerUsersResource = providerRealmResource.users(); - String providerUserID = createUser(bc.providerRealmName(), USERNAME, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL); + String providerUserID = createUser(bc.providerRealmName(), USERNAME, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL, + user -> user.setEmailVerified(true)); UserResource providerUserResource = providerUsersResource.get(providerUserID); try { IdentityProviderResource consumerIdentityResource = getIdentityProviderResource(); IdentityProviderRepresentation idProvider = consumerIdentityResource.toRepresentation(); updateIdPSyncMode(idProvider, consumerIdentityResource, - isForceSync ? IdentityProviderSyncMode.FORCE : IdentityProviderSyncMode.IMPORT); + isForceSync ? IdentityProviderSyncMode.FORCE : IdentityProviderSyncMode.IMPORT, isTrustEmail); + // login to create the user in the consumer realm oauth.clientId("broker-app"); loginPage.open(bc.consumerRealmName()); @@ -646,17 +658,28 @@ private void checkUpdatedUserAttributesIdP(boolean isForceSync) { UserResource consumerUserResource = consumerRealmResource.users().get(consumerUserID); checkFederatedIdentityLink(consumerUserResource, providerUserID, USERNAME); + assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.equalTo(isTrustEmail)); AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), USERNAME); AccountHelper.logout(adminClient.realm(bc.providerRealmName()), USERNAME); + // set email verified to true on the consumer resource + consumerUser = consumerUserResource.toRepresentation(); + consumerUser.setEmailVerified(true); + consumerUserResource.update(consumerUser); + consumerUserResource = consumerRealmResource.users().get(consumerUserID); + assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.is(true)); + + // modify provider user with the new values UserRepresentation providerUser = providerUserResource.toRepresentation(); providerUser.setUsername(NEW_USERNAME); providerUser.setFirstName(NEW_FIRST_NAME); providerUser.setLastName(NEW_LAST_NAME); providerUser.setEmail(NEW_EMAIL); + providerUser.setEmailVerified(true); providerUserResource.update(providerUser); + // login again to force sync if force mode oauth.clientId("broker-app"); loginPage.open(bc.consumerRealmName()); @@ -674,7 +697,10 @@ private void checkUpdatedUserAttributesIdP(boolean isForceSync) { assertThat(userRepresentation.getFirstName(), Matchers.equalTo(isForceSync ? NEW_FIRST_NAME : FIRST_NAME)); assertThat(userRepresentation.getLastName(), Matchers.equalTo(isForceSync ? NEW_LAST_NAME : LAST_NAME)); + consumerUserResource = consumerRealmResource.users().get(consumerUserID); checkFederatedIdentityLink(consumerUserResource, providerUserID, isForceSync ? NEW_USERNAME : USERNAME); + // the email verified should be reverted to false if force-sync and not trust-email + assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.equalTo(!isForceSync || isTrustEmail)); } finally { providerUsersResource.delete(providerUserID); } @@ -695,21 +721,25 @@ private void checkFederatedIdentityLink(UserResource userResource, String userID assertThat(federatedIdentity.getUserName(), Matchers.equalTo(username)); } - private void updateIdPSyncMode(IdentityProviderRepresentation idProvider, IdentityProviderResource idProviderResource, IdentityProviderSyncMode syncMode) { + private void updateIdPSyncMode(IdentityProviderRepresentation idProvider, IdentityProviderResource idProviderResource, + IdentityProviderSyncMode syncMode, boolean trustEmail) { assertThat(idProvider, Matchers.notNullValue()); assertThat(idProviderResource, Matchers.notNullValue()); assertThat(syncMode, Matchers.notNullValue()); - if (idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE).equals(syncMode.name())) { + if (idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE).equals(syncMode.name()) + && idProvider.isTrustEmail() == trustEmail) { return; } idProvider.getConfig().put(IdentityProviderModel.SYNC_MODE, syncMode.name()); + idProvider.setTrustEmail(trustEmail); idProviderResource.update(idProvider); idProvider = idProviderResource.toRepresentation(); assertThat("Cannot get Identity Provider", idProvider, Matchers.notNullValue()); assertThat("Sync mode didn't change", idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE), Matchers.equalTo(syncMode.name())); + assertThat("TrustEmail didn't change", idProvider.isTrustEmail(), Matchers.equalTo(trustEmail)); } private UserRepresentation getFederatedIdentity() {
Vulnerability mechanics
Generated 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-c7xw-p58w-h6fjghsaADVISORY
- access.redhat.com/security/cve/CVE-2023-0105ghsaWEB
- bugzilla.redhat.com/show_bug.cgighsaWEB
- github.com/keycloak/keycloak/commit/87a50d3ba790b049e436c9925874f9b418af7988ghsaWEB
- github.com/keycloak/keycloak/security/advisories/GHSA-c7xw-p58w-h6fjghsaWEB
News mentions
0No linked articles in our index yet.