ANT-2026-S8PHCV74 · cloudfoundry/uaa
auth-bypass medium
Severity Claude high · Security research firm medium · Maintainer -
Discovered by Claude Mythos Preview
Anthropic's analysis of this finding, sealed at approval.
ANT-2026-S8PHCV74: DenyAccessToUaaAdvice case-sensitive check bypassed on MySQL: zones.write overwrites system zone
DenyAccessToUaaAdvice.checkIdentityZoneId() and GeneralIdentityZoneValidator block modification of the system zone by comparing the incoming ID to the literal string "uaa" with case-sensitive String.equals(). On MySQL with the default utf8mb4_0900_ai_ci collation, a WHERE id='UAA' query still resolves to the system-zone row. A caller holding only zones.write — a tenant-management authority explicitly fenced off from the system zone — can PUT /identity-zones/UAA with an uppercase id in both the path and body, pass the advice check, and overwrite the system zone's tokenPolicy with attacker-controlled JWT signing keys. The attacker can then forge RS256 tokens with uaa.admin scope and take over the platform.
Target
Project: cloudfoundry/uaa
Location: server/src/main/java/org/cloudfoundry/identity/uaa/zone/DenyAccessToUaaAdvice.java:15
Discovery: static analysis — not yet dynamically reproduced
Technical Details
The guard uses IdentityZone.getUaaZoneId().equals(identityZoneId), a case-sensitive comparison, so "UAA" is not blocked. The persistence layer on MySQL is case-insensitive by default, so the update lands on the real 'uaa' row. The AOP wiring at IdentityZoneAopConfig.java:43-46 checks both the path param and body id, so supplying uppercase in both defeats every check.
Reproduction
- Obtain a token for a client with authorities zones.read, zones.write, uaa.resource, scim.zones (no uaa.admin).
- Send PUT /identity-zones/UAA with body id "UAA" and a config.tokenPolicy containing an attacker-controlled activeKeyId and RSA signing key; receive HTTP 200.
- Confirm via GET /token_keys that the attacker key is now the active system signer (takes effect immediately, no restart).
- Forge an RS256 JWT with kid=attacker-key and scope [uaa.admin], signed with the attacker's private key.
- Call privileged endpoints (e.g. GET /Users) with the forged token; receive HTTP 200.
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Acknowledgement
This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-S8PHCV74.
Reference: ANT-2026-S8PHCV74
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure
Independent triage by an external party.
- Verdict
- true positive
- Severity
- medium
The change that resolved this finding.
diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/DenyAccessToUaaAdvice.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/DenyAccessToUaaAdvice.java
index a24f11ef1df..68b038b48a8 100644
--- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/DenyAccessToUaaAdvice.java
+++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/DenyAccessToUaaAdvice.java
@@ -7,13 +7,13 @@
public class DenyAccessToUaaAdvice {
public void checkIdentityZone(IdentityZone identityZone) {
- if (identityZone != null && identityZone.isUaa()) {
+ if (identityZone != null && IdentityZone.getUaaZoneId().equalsIgnoreCase(identityZone.getId())) {
throw new AccessDeniedException("Access to UAA is not allowed.");
}
}
public void checkIdentityZoneId(String identityZoneId) {
- if (IdentityZone.getUaaZoneId().equals(identityZoneId)) {
+ if (IdentityZone.getUaaZoneId().equalsIgnoreCase(identityZoneId)) {
throw new AccessDeniedException("Access to UAA is not allowed.");
}
}
diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneValidator.java
index 0014b48f287..6ecdf879e64 100644
--- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneValidator.java
+++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/GeneralIdentityZoneValidator.java
@@ -15,7 +15,7 @@ public GeneralIdentityZoneValidator(final IdentityZoneConfigurationValidator con
@Override
public IdentityZone validate(IdentityZone identityZone, Mode mode) throws InvalidIdentityZoneDetailsException {
- if (IdentityZoneHolder.getUaaZone().getId().equals(identityZone.getId()) && !identityZone.isActive()) {
+ if (IdentityZoneHolder.getUaaZone().getId().equalsIgnoreCase(identityZone.getId()) && !identityZone.isActive()) {
throw new InvalidIdentityZoneDetailsException("The default zone cannot be set inactive.", null);
}
if (DEFAULT_ZONE_SUBDOMAIN_PATH.equalsIgnoreCase(identityZone.getSubdomain())) {https://github.com/cloudfoundry/uaa/commit/65118219a21b39b6ed561712f83d460bea3a15f3
Dates from discovery through public reveal.
- 2026-04-09 Reported to tracker
- 2026-05-19 Sent to maintainer
- 2026-05-19 Patch released
- 2026-08-17 Publicly revealed
SHA-3-512 hash:
52b0e75e358db4af90813b97fd908de26fdf9b48c4dc4e36e22b2a82ec68fadae72dc72982cb8b3580ccdf5a692ce110ea8f4bfc308964f0394f0e983f0d77e9
Committed 2026-05-08 09:46 PT
Revealed 2026-08-17 17:36 PT
Verify (download preimage.json)
Show preimage JSON
{
"ant_id": "ANT-2026-S8PHCV74",
"bug_class": "auth_bypass",
"claude_severity": "high",
"commit_sha": null,
"created_at": "2026-04-09T18:20:44+00:00",
"description": "DenyAccessToUaaAdvice.checkIdentityZoneId() and GeneralIdentityZoneValidator block modification of the system zone by comparing the incoming ID to the literal string \"uaa\" with case-sensitive String.equals(). On MySQL with the default utf8mb4_0900_ai_ci collation, a WHERE id='UAA' query still resolves to the system-zone row. A caller holding only zones.write — a tenant-management authority explicitly fenced off from the system zone — can PUT /identity-zones/UAA with an uppercase id in both the path and body, pass the advice check, and overwrite the system zone's tokenPolicy with attacker-controlled JWT signing keys. The attacker can then forge RS256 tokens with uaa.admin scope and take over the platform.",
"discovered_at": null,
"location": "server/src/main/java/org/cloudfoundry/identity/uaa/zone/DenyAccessToUaaAdvice.java:15",
"poc_sha256": null,
"preimage_version": 1,
"project": "cloudfoundry/uaa",
"reproduction": [
"1. Obtain a token for a client with authorities zones.read, zones.write, uaa.resource, scim.zones (no uaa.admin).",
"2. Send PUT /identity-zones/UAA with body id \"UAA\" and a config.tokenPolicy containing an attacker-controlled activeKeyId and RSA signing key; receive HTTP 200.",
"3. Confirm via GET /token_keys that the attacker key is now the active system signer (takes effect immediately, no restart).",
"4. Forge an RS256 JWT with kid=attacker-key and scope [uaa.admin], signed with the attacker's private key.",
"5. Call privileged endpoints (e.g. GET /Users) with the forged token; receive HTTP 200."
],
"technical_details": "The guard uses IdentityZone.getUaaZoneId().equals(identityZoneId), a case-sensitive comparison, so \"UAA\" is not blocked. The persistence layer on MySQL is case-insensitive by default, so the update lands on the real 'uaa' row. The AOP wiring at IdentityZoneAopConfig.java:43-46 checks both the path param and body id, so supplying uppercase in both defeats every check.",
"title": "DenyAccessToUaaAdvice case-sensitive check bypassed on MySQL: zones.write overwrites system zone",
"vendor_severity": "medium"
}