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.
| Package | Affected versions | Patched versions |
|---|---|---|
org.openidentityplatform.openam:openam-oauth2Maven | < 16.0.3 | 16.0.3 |
Patches
14254b34b2b8bUsing arbitrary OIDC requested claims values in id_token and user_info is allowed
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- github.com/advisories/GHSA-39hr-239p-fhqcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-64099ghsaADVISORY
- github.com/OpenIdentityPlatform/OpenAM/commit/4254b34b2b8b4867f2e7fccfac73904213d48510ghsaWEB
- github.com/OpenIdentityPlatform/OpenAM/releases/tag/16.0.3ghsaWEB
- github.com/OpenIdentityPlatform/OpenAM/security/advisories/GHSA-39hr-239p-fhqcnvdWEB
News mentions
0No linked articles in our index yet.