VYPR
Low severity2.7NVD Advisory· Published Feb 2, 2026· Updated Apr 15, 2026

CVE-2025-13881

CVE-2025-13881

Description

A flaw was found in Keycloak Admin API. This vulnerability allows an administrator with limited privileges to retrieve sensitive custom attributes via the /unmanagedAttributes endpoint, bypassing User Profile visibility settings.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.keycloak:keycloak-servicesMaven
>= 26.5.0, < 26.5.226.5.2
org.keycloak:keycloak-servicesMaven
< 26.4.926.4.9

Affected products

1

Patches

2
1d7ab8d5fb14

fixed unmanaged attributes to not allow writing when only admin can view policy is enabled

https://github.com/keycloak/keycloakSebastian SchusterJan 21, 2026via ghsa
3 files changed · +83 7
  • server-spi-private/src/main/java/org/keycloak/userprofile/DefaultAttributes.java+2 6 modified
    @@ -260,20 +260,16 @@ public Map<String, List<String>> getWritable() {
             Map<String, List<String>> attributes = new HashMap<>(this);
     
             for (String name : nameSet()) {
    -            AttributeMetadata metadata = getMetadata(name);
                 RealmModel realm = session.getContext().getRealm();
     
    -            if ((UserModel.USERNAME.equals(name) && realm.isRegistrationEmailAsUsername())
    -                || isReadableOrWritableDuringRegistration(name)
    -                || !isManagedAttribute(name)) {
    +            if ((UserModel.USERNAME.equals(name) && realm.isRegistrationEmailAsUsername())) {
                     continue;
                 }
     
    -            if (metadata == null || !metadata.canEdit(createAttributeContext(metadata))) {
    +            if (isReadOnly(name)) {
                     attributes.remove(name);
                 }
             }
    -
             return attributes;
         }
     
    
  • tests/base/src/test/java/org/keycloak/tests/admin/user/UserAttributesTest.java+72 0 modified
    @@ -4,14 +4,18 @@
     import org.hamcrest.Matchers;
     import org.junit.jupiter.api.Assertions;
     import org.junit.jupiter.api.Test;
    +
    +import org.keycloak.admin.client.resource.UserProfileResource;
     import org.keycloak.models.LDAPConstants;
     import org.keycloak.models.UserModel;
     import org.keycloak.representations.idm.ErrorRepresentation;
     import org.keycloak.representations.idm.RealmRepresentation;
     import org.keycloak.representations.idm.UserRepresentation;
    +import org.keycloak.representations.userprofile.config.UPConfig;
     import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
     import org.keycloak.testframework.server.KeycloakServerConfig;
     import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
    +import org.keycloak.testsuite.util.userprofile.UserProfileUtil;
     
     import java.util.ArrayList;
     import java.util.Collections;
    @@ -142,6 +146,74 @@ public void attributes() {
             assertEquals(1, user1.getAttributes().size());
         }
     
    +    @Test
    +    public void unmanagedAttributes() {
    +        UserProfileResource userProfileRes = managedRealm.admin().users().userProfile();
    +        UserProfileUtil.enableUnmanagedAttributes(userProfileRes);
    +        adminEvents.poll();
    +
    +        UserRepresentation user1 = new UserRepresentation();
    +        user1.setUsername("user1");
    +        user1.singleAttribute("attr1", "value1");
    +        user1.singleAttribute("unmanaged1", "value2");
    +        String user1Id = createUser(user1);
    +
    +        //check visibility of attributes depending on unmanaged attribute policy
    +
    +        //with unmanaged attributes enabled, managed and unmanaged attributes are visible
    +        user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
    +        assertEquals(2, user1.getAttributes().size());
    +        assertAttributeValue("value1", user1.getAttributes().get("attr1"));
    +        assertAttributeValue("value2", user1.getAttributes().get("unmanaged1"));
    +
    +        //with unmanaged attributes disabled, only managed attributes are visible
    +        UserProfileUtil.disableUnmanagedAttributes(userProfileRes);
    +        adminEvents.poll();
    +        user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
    +        assertEquals(1, user1.getAttributes().size());
    +        assertAttributeValue("value1", user1.getAttributes().get("attr1"));
    +
    +        //with ADMIN_EDIT policy, managed and unmanaged attributes are visible
    +        UserProfileUtil.setUnmanagedAttributesPolicy(userProfileRes, UPConfig.UnmanagedAttributePolicy.ADMIN_EDIT);
    +        adminEvents.poll();
    +        user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
    +        assertEquals(2, user1.getAttributes().size());
    +        assertAttributeValue("value1", user1.getAttributes().get("attr1"));
    +        assertAttributeValue("value2", user1.getAttributes().get("unmanaged1"));
    +
    +        //with ADMIN_VIEW policy, managed and unmanaged attributes are visible
    +        UserProfileUtil.setUnmanagedAttributesPolicy(userProfileRes, UPConfig.UnmanagedAttributePolicy.ADMIN_VIEW);
    +        adminEvents.poll();
    +        user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
    +        assertEquals(2, user1.getAttributes().size());
    +        assertAttributeValue("value1", user1.getAttributes().get("attr1"));
    +        assertAttributeValue("value2", user1.getAttributes().get("unmanaged1"));
    +
    +        //check updating of attributes depending on unmanaged attribute policy
    +
    +        //with ADMIN_VIEW policy, only managed attributes can be updated
    +        user1.singleAttribute("attr1", "updated1");
    +        user1.singleAttribute("unmanaged1", "updated2");
    +        user1.singleAttribute("unmanaged2", "value3");
    +        updateUser(managedRealm.admin().users().get(user1Id), user1);
    +        user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
    +        assertEquals(2, user1.getAttributes().size());
    +        assertAttributeValue("updated1", user1.getAttributes().get("attr1"));
    +        assertAttributeValue("value2", user1.getAttributes().get("unmanaged1"));
    +
    +        //with ADMIN_EDIT policy, managed and unmanaged attributes can be updated
    +        UserProfileUtil.setUnmanagedAttributesPolicy(userProfileRes, UPConfig.UnmanagedAttributePolicy.ADMIN_EDIT);
    +        adminEvents.poll();
    +        user1.singleAttribute("unmanaged1", "updated2");
    +        user1.singleAttribute("unmanaged2", "value3");
    +        updateUser(managedRealm.admin().users().get(user1Id), user1);
    +        user1 = managedRealm.admin().users().get(user1Id).toRepresentation();
    +        assertEquals(3, user1.getAttributes().size());
    +        assertAttributeValue("updated1", user1.getAttributes().get("attr1"));
    +        assertAttributeValue("updated2", user1.getAttributes().get("unmanaged1"));
    +        assertAttributeValue("value3", user1.getAttributes().get("unmanaged2"));
    +    }
    +
         @Test
         public void updateUserWithReadOnlyAttributes() {
             // Admin is able to update "usercertificate" attribute
    
  • tests/utils-shared/src/main/java/org/keycloak/testsuite/util/userprofile/UserProfileUtil.java+9 1 modified
    @@ -59,8 +59,16 @@ public static UPConfig setUserProfileConfiguration(RealmResource testRealm, Stri
         }
     
         public static UPConfig enableUnmanagedAttributes(UserProfileResource upResource) {
    +        return setUnmanagedAttributesPolicy(upResource, UPConfig.UnmanagedAttributePolicy.ENABLED);
    +    }
    +
    +    public static UPConfig disableUnmanagedAttributes(UserProfileResource upResource) {
    +        return setUnmanagedAttributesPolicy(upResource, null);
    +    }
    +
    +    public static UPConfig setUnmanagedAttributesPolicy(UserProfileResource upResource, UPConfig.UnmanagedAttributePolicy policy) {
             UPConfig cfg = upResource.getConfiguration();
    -        cfg.setUnmanagedAttributePolicy(UPConfig.UnmanagedAttributePolicy.ENABLED);
    +        cfg.setUnmanagedAttributePolicy(policy);
             upResource.update(cfg);
             return cfg;
         }
    
c5c83d6604d4

Fix test failures

https://github.com/keycloak/keycloakSebastian SchusterJan 14, 2026via ghsa
1 file changed · +6 0
  • server-spi-private/src/main/java/org/keycloak/userprofile/DefaultAttributes.java+6 0 modified
    @@ -261,6 +261,12 @@ public Map<String, List<String>> getWritable() {
             Map<String, List<String>> attributes = new HashMap<>(this);
     
             for (String name : nameSet()) {
    +            RealmModel realm = session.getContext().getRealm();
    +
    +            if ((UserModel.USERNAME.equals(name) && realm.isRegistrationEmailAsUsername())) {
    +                continue;
    +            }
    +
                 if (isReadOnly(name)) {
                     attributes.remove(name);
                 }
    

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

10

News mentions

0

No linked articles in our index yet.