VYPR
High severityNVD Advisory· Published Nov 12, 2025· Updated Apr 15, 2026

CVE-2025-64099

CVE-2025-64099

Description

Open Access Management (OpenAM) is an access management solution. In versions prior to 16.0.0, if the "claims_parameter_supported" parameter is activated, it is possible, thanks to the "oidc-claims-extension.groovy" script, to inject the value of one's choice into a claim contained in the id_token or in the user_info. In the request of an authorize function, a claims parameter containing a JSON file can be injected. This JSON file allows attackers to customize the claims returned by the "id_token" and "user_info" files. This allows for a very wide range of vulnerabilities depending on how clients use claims. For example, if some clients rely on an email field to identify a user, an attacker can choose the email address they want, and therefore assume any identity they choose. Version 16.0.0 fixes the issue.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

OpenAM prior to 16.0.0 allows injection of arbitrary OIDC claims via the claims parameter, enabling identity spoofing if clients rely on claims like email.

Vulnerability

Details

OpenAM versions prior to 16.0.0 contain a vulnerability in the OIDC claims handling. When the claims_parameter_supported option is enabled, the oidc-claims-extension.groovy script fails to validate the values supplied in the claims parameter of an authorization request. An attacker can inject arbitrary JSON to set any claim value in the returned id_token or user_info response [1][3].

Exploitation

An attacker can craft an authorization request with a malicious claims parameter containing a JSON object that specifies desired claim values. No special privileges are required; the attacker only needs to initiate an OIDC authorization flow against a vulnerable OpenAM instance. The injected claims are then included in the tokens or user info endpoint responses [1].

Impact

The impact depends on how relying parties (clients) use the claims. For example, if a client uses the email claim to identify users, an attacker can set the email to any value, effectively assuming any identity. This can lead to account takeover, privilege escalation, or unauthorized access to resources [1].

Mitigation

The issue is fixed in OpenAM version 16.0.0 [4]. The commit [3] shows the specific code changes that prevent arbitrary claim injection. Users should upgrade to 16.0.0 or later. No workaround is documented.

AI Insight generated on May 19, 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.openidentityplatform.openam:openam-oauth2Maven
< 16.0.316.0.3

Patches

1
4254b34b2b8b

Using arbitrary OIDC requested claims values in id_token and user_info is allowed

https://github.com/OpenIdentityPlatform/OpenAMMaxim ThomasOct 29, 2025via ghsa
3 files changed · +34 14
  • openam-oauth2/src/test/java/org/forgerock/openam/openidconnect/OidcClaimsExtensionTest.java+14 4 modified
    @@ -12,6 +12,7 @@
      * information: "Portions copyright [year] [name of copyright owner]".
      *
      * Copyright 2015-2016 ForgeRock AS.
    + * Portions copyright 2025 3A Systems LLC.
      */
     
     package org.forgerock.openam.openidconnect;
    @@ -71,6 +72,10 @@ public void setupScript() throws Exception {
         @BeforeMethod
         public void setup() throws Exception {
             this.logger = mock(Debug.class);
    +        doAnswer(invocationOnMock -> {
    +          System.out.println(invocationOnMock.getArguments()[0]);
    +          return null;
    +        }).when(logger).warning(anyString());
             this.ssoToken = mock(SSOToken.class);
             this.identity = mock(AMIdentity.class);
             this.accessToken = new StatefulAccessToken(json(object()), OAuth2Constants.Token.OAUTH_ACCESS_TOKEN, "id");
    @@ -106,18 +111,20 @@ public void testProfileScope() throws Exception {
         public void testRequestedClaims() throws Exception {
             // Given
             Map<String, Set<String>> requestedClaims = new HashMap<String, Set<String>>();
    -        requestedClaims.put("given_name", asSet("fred"));
    +        requestedClaims.put("given_name", asSet("joe", "fred"));
             requestedClaims.put("family_name", asSet("flintstone"));
    +
             Bindings variables = testBindings(asSet("profile"), requestedClaims);
             when(identity.getAttribute("cn")).thenReturn(asSet("Joe Bloggs"));
    +        when(identity.getAttribute("sn")).thenReturn(asSet("bloggs"));
    +        when(identity.getAttribute("givenname")).thenReturn(asSet("joe"));
     
             // When
             UserInfoClaims result = scriptEvaluator.evaluateScript(script, variables);
     
             // Then
             assertThat(result.getValues()).containsOnly(
    -                entry("given_name", "fred"),
    -                entry("family_name", "flintstone"),
    +                entry("given_name", "joe"),
                     entry("name", "Joe Bloggs"));
     
             assertThat(result.getCompositeScopes()).containsOnlyKeys("profile");
    @@ -128,7 +135,7 @@ public void testRequestedClaims() throws Exception {
             verify(identity).getAttribute("cn");
             verify(identity).getAttribute("preferredlocale");
             verify(identity).getAttribute("preferredtimezone");
    -        verifyNoMoreInteractions(identity);
    +        verify(identity).getAttribute("sn");
         }
     
         @Test
    @@ -139,6 +146,9 @@ public void testRequestedClaimsNoScope() throws Exception {
             requestedClaims.put("family_name", asSet("flintstone"));
             Bindings variables = testBindings(asSet("openid"), requestedClaims);
     
    +        when(identity.getAttribute("sn")).thenReturn(asSet("flintstone"));
    +        when(identity.getAttribute("givenname")).thenReturn(asSet("fred"));
    +
             // When
             UserInfoClaims result = scriptEvaluator.evaluateScript(script, variables);
     
    
  • openam-oauth2/src/test/resources/oidc-claims-extension.groovy+10 5 modified
    @@ -12,6 +12,7 @@
     * information: "Portions copyright [year] [name of copyright owner]".
     *
     * Copyright 2014-2016 ForgeRock AS.
    +* Portions copyright 2025 3A Systems LLC.
     */
     import com.iplanet.sso.SSOException
     import com.sun.identity.idm.IdRepoException
    @@ -52,12 +53,16 @@ def fromSet = { claim, attr ->
     }
     
     attributeRetriever = { attribute, claim, identity, requested ->
    +    def attr = fromSet(claim, identity.getAttribute(attribute))
         if (requested == null || requested.isEmpty()) {
    -        fromSet(claim, identity.getAttribute(attribute))
    -    } else if (requested.size() == 1) {
    -        requested.iterator().next()
    +        attr
         } else {
    -        throw new RuntimeException("No selection logic for $claim defined. Values: $requested")
    +        if (requested.contains(attr)) {
    +            attr
    +        } else {
    +            logger.warning("OpenAMScopeValidator.getUserInfo(): $claim attribute=$attr does not match the requested value=$requested");
    +            null
    +        }
         }
     }
     
    @@ -106,7 +111,7 @@ def computedClaims = scopes.findAll { s -> !"openid".equals(s) && scopeClaimsMap
         map << scopeClaims.findAll { c -> !requestedClaims.containsKey(c) }.collectEntries([:]) { claim -> computeClaim(claim, null) }
     }.findAll { map -> map.value != null } << requestedClaims.collectEntries([:]) { claim, requestedValue ->
         computeClaim(claim, requestedValue)
    -}
    +}.findAll { map -> map.value != null }
     
     def compositeScopes = scopeClaimsMap.findAll { scope ->
         scopes.contains(scope.key)
    
  • openam-scripting/src/main/groovy/oidc-claims-extension.groovy+10 5 modified
    @@ -12,6 +12,7 @@
     * information: "Portions copyright [year] [name of copyright owner]".
     *
     * Copyright 2014-2016 ForgeRock AS.
    +* Portions copyright 2025 3A Systems LLC.
     */
     import com.iplanet.sso.SSOException
     import com.sun.identity.idm.IdRepoException
    @@ -52,12 +53,16 @@ def fromSet = { claim, attr ->
     }
     
     attributeRetriever = { attribute, claim, identity, requested ->
    +    def attr = fromSet(claim, identity.getAttribute(attribute))
         if (requested == null || requested.isEmpty()) {
    -        fromSet(claim, identity.getAttribute(attribute))
    -    } else if (requested.size() == 1) {
    -        requested.iterator().next()
    +        attr
         } else {
    -        throw new RuntimeException("No selection logic for $claim defined. Values: $requested")
    +        if (requested.contains(attr)) {
    +            attr
    +        } else {
    +            logger.warning("OpenAMScopeValidator.getUserInfo(): $claim attribute=$attr does not match the requested value=$requested");
    +            null
    +        }
         }
     }
     
    @@ -106,7 +111,7 @@ def computedClaims = scopes.findAll { s -> !"openid".equals(s) && scopeClaimsMap
         map << scopeClaims.findAll { c -> !requestedClaims.containsKey(c) }.collectEntries([:]) { claim -> computeClaim(claim, null) }
     }.findAll { map -> map.value != null } << requestedClaims.collectEntries([:]) { claim, requestedValue ->
         computeClaim(claim, requestedValue)
    -}
    +}.findAll { map -> map.value != null }
     
     def compositeScopes = scopeClaimsMap.findAll { scope ->
         scopes.contains(scope.key)
    

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

News mentions

0

No linked articles in our index yet.