VYPR
Moderate severityNVD Advisory· Published Jan 11, 2023· Updated Apr 9, 2025

CVE-2023-0105

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.

PackageAffected versionsPatched versions
org.keycloak:keycloak-coreMaven
< 22.0.122.0.1

Affected products

2

Patches

1
87a50d3ba790

Revert emailVerified to false if email modified on force-sync non-trusted broker

https://github.com/keycloak/keycloakrmartincJun 23, 2023via ghsa
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

News mentions

0

No linked articles in our index yet.