Feb 10, 2026·6 min read·6 visits
Keycloak checks the ID of the driver but lets the whole carload of robbers in. A flaw in `UserManagedPermissionService` allowed users to update multi-resource policies by only proving ownership of the *first* resource. This lets User A modify access rules for User B's resources.
A logic flaw in Keycloak's User-Managed Access (UMA) Protection API allows for horizontal privilege escalation. By exploiting how the system validates resource ownership in shared policies, an attacker can modify permissions for resources they do not own, effectively hijacking access controls for other users.
User-Managed Access (UMA) 2.0 is a beast of a specification. It’s designed to give users granular control over their resources—think "Alice wants to share her vacation photos with Bob for 24 hours, but only if he promises not to download them." Implementing this in the real world requires a labyrinth of policies, permissions, and resource sets. Keycloak, the 800-pound gorilla of open-source Identity and Access Management (IAM), implements this via its Protection API.
But here's the thing about complex specifications: they often lull developers into a false sense of security regarding simple logic. CVE-2025-14778 is not a buffer overflow or a fancy deserialization gadget chain. It's a logic error so simple it hurts. It’s the digital equivalent of a bouncer checking the ID of the first person in a line of ten and waving everyone else through.
In this vulnerability, the component responsible for managing permissions—UserManagedPermissionService—forgot that a single policy might govern multiple resources. By validating ownership against only one of them, it opened the door for what we call Horizontal Privilege Escalation. If you share a policy with me, I can suddenly decide who gets to see your data.
Let's strip away the IAM jargon and look at the logic. When a user sends a PUT request to update a UMA policy (permission), Keycloak needs to answer one question: "Does the person making this request actually own the resource being modified?"
In the vulnerable version of Keycloak, the code answered this question by grabbing the associated resource ID from the policy. The problem? A UMA policy can be associated with a list of resources. The code effectively said:
> "Grab the first resource ID from the list. Does the user own this one? Yes? Cool, apply the changes to the whole list."
This is the "Party Bus" fallacy. If the driver has a ticket, the bouncer assumes everyone on the bus has a ticket. If an attacker can craft or interact with a policy that includes their own resource (Resource A) and a victim's resource (Resource B), they become the driver of that bus. When they send an update command, Keycloak checks Resource A, sees the attacker owns it, gives the thumbs up, and inadvertently applies the new rules to Resource B as well.
The fix provided in Pull Request #46154 exposes the smoking gun. The vulnerability existed in the update method of UserManagedPermissionService.java.
The Vulnerable Logic (Conceptual): In previous versions, the code implicitly trusted that checking one resource was enough. It didn't explicitly validate the integrity of the resource set being updated.
The Fix: The patch introduces strict validation to ensure that a user cannot change the resource association during an update if it involves more than the original resource they own. Look at this defensive check added in the patch:
// Get the ID of the resource associated with this permission
String resourceId = getAssociatedResourceId(policyId);
// Get the resources the user is trying to set in this update
Set<String> resources = Optional.ofNullable(representation.getResources()).orElse(Set.of());
// THE FIX: strict validation
if (resources.isEmpty() || (resources.size() == 1 && resources.contains(resourceId))) {
// If we are only touching the original resource, proceed to check ownership
checkRequest(resourceId, representation);
// Proceed with update...
} else {
// BLOCK: You cannot change the resource scope or add resources you don't own
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST,
"Uma permission resource cannot be changed", Response.Status.BAD_REQUEST);
}Analysis:
The developers effectively locked down the API. You can no longer smuggle in new resources or modify a multi-resource set via this endpoint. If the resource list in your request isn't exactly just the one you own (or empty, implying no change), the server throws a BAD_REQUEST. This kills the attack vector dead by enforcing a 1:1 mapping during updates.
To exploit this, an attacker doesn't need to be an admin. They just need to be a regular user with the ability to create resources and interact with UMA policies. Here is the attack chain:
1. The Setup Imagine a collaborative environment (like a document sharing system) using Keycloak.
Document_Evil.Document_Sensitive.2. The Link
A UMA policy is created that groups these resources. This could happen legitimately in a shared workspace scenario, or the attacker might manipulate a "collection" mechanism to group Document_Evil and Document_Sensitive under one permission set.
3. The Attack
The attacker sends a PUT request to /realms/{realm}/authz/protection/uma-policy/{policyId}.
{
"name": "Attacker Controlled Policy",
"description": "I am changing the rules now.",
"logic": "POSITIVE",
"decisionStrategy": "UNANIMOUS",
"resources": ["Document_Evil", "Document_Sensitive"],
"policies": ["Grant_Access_To_Attacker"]
}4. The Execution
Keycloak's checkRequest looks at the policy. It sees Document_Evil. It asks: "Does Attacker own Document_Evil?" The answer is YES.
The system approves the request. The policy is updated. Now, Grant_Access_To_Attacker applies to both documents. The attacker has successfully granted themselves full access to the victim's sensitive document, bypassing the fact that they do not own it.
While the CVSS score is a moderate 5.4 (due to the complexity of needing a shared policy context), the impact in the right environment is critical. This is Broken Access Control in its purest form.
In highly regulated industries (Finance, Healthcare) where UMA is often used for consent management, this could allow a malicious actor to rewrite patient consent forms or financial access rules simply by bundling them with a record they control.
The mitigation is straightforward: Update Keycloak. The patch doesn't just fix the bug; it changes the behavior of the API to be strict about resource boundaries during updates.
Patching Strategy:
There are no viable workarounds. Red Hat has explicitly stated that trying to mitigate this via configuration is insufficient. You need the code change that enforces the logic check. If you are running a custom build of Keycloak, you need to cherry-pick PR #46154 into your branch and rebuild the services module.
Lesson Learned: When validating permissions for a collection, validate the whole collection. Iterating through a list is computationally cheap; a data breach is expensive.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
Red Hat build of Keycloak Red Hat | < 26.2.13-1 | 26.2.13-1 |
Red Hat build of Keycloak Red Hat | < 26.4.9-1 | 26.4.9-1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-266 |
| Attack Vector | Network (Remote) |
| CVSS Score | 5.4 (Medium) |
| Impact | Horizontal Privilege Escalation |
| Exploit Status | PoC Available (Internal/Theoretical) |
| KEV Status | Not Listed |
Incorrect Privilege Assignment