Keycloak UMA vulnerabilities
Published:
Updated:
Keycloak UMA's implementation seems tricky to me.
Table of content
Summary
Findings:
- UMA token requests do not verify the end user consent.
- A RPT (i.e. UMA access token) can be obtained without client credentials, using only a user access token (for example another RPT).
Tested on:
- Keycloak 26.1.4;
- Keycloak 26.3.0.
Overview of Keycloak UMA implementation
Keycloak has an implementation of UMA with a lot of caveats and deviations from the specification:
- The UMA resources are not associated with a end-user. Instead, they are always owned by the resource server itself.
- As a consequence, in order to declare a new resource, the resource server,
- obtains an access token on its own behalf using using credentials grant;
- uses this access token on the resource registration endpoint.
- There is no support for interactive-claims gathering. Therefore, there is no support for checking the user consent in the UMA flow.
- Keycloak accepts user ID tokens, user access tokens (including RPTs) and user refresh tokens as
claim_token
. - Alternatively, the client can pass the user access token[1] as a bearer token instead (without client authentication).
- A RPT can be obtained using only a user access token (for example another RPT), without client credentials.
Summary: The Keycloak token endpoint UMA grant does not require client authentication. A RPT can be obtained using a user access token as authentication.
The following table summarizes the different accepted combinations for UMA token requests (…:grant-type:uma-ticket
). More details are available in appendix.
Authentication | claim_token |
---|---|
client credentials | user ID token |
client credentials | user access token |
client credentials | user refresh token |
user access token | none |
Lack of user consent verification in UMA token request
Summary: a client application can obtain an UMA RPT without the requesting-party's consent.
Keycloak currently accepts a uma-ticket
grant authenticated using a end-user token in order to provide an RPT on behalf of this user. This end-user token can be:
- a user ID token;
- a user access token;
- a user refresh token.
POST .../token HTTP/1.1
Authorization: Bearer {USER_ACCESS_TOKEN}
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:uma-ticket
&ticket={TICKET}
The user consent is not required. In particular, logging in an application using OpenID connect, or sending a resource server an access token implicitly gives to this application the ability to access all UMA resources (of all resource servers) this user has access to without the user being aware of it.
Fix: By default, the authorization server should require the requesting-party's consent (using interactive claims-gathering) before issuing a RPT to a client application the first time this client application requests access to a resource and scope on behalf of this requesting-party.
See the Requirements for Pre-Established Trust Regarding Claim Tokens section of the UMA specification:
A malicious client could push a claim token to the authorization server (revealing the claims therein; see Section 6.2) to seek resource access on its own behalf prior to any opportunity for an end-user requesting party to authorize claims collection. It is RECOMMENDED either for trust relationships established by the ecosystem parties to include prior requesting party authorization as required, or for end-user requesting party authorization to be gathered interactively prior to claims pushing, as described in Section 3.3.2.
OAuth 2.0 Threat Model and Security Considerations discusses user consent:
After authenticating the end user, the authorization server should ask him/her for consent. In this context, the authorization server should explain to the end user the purpose, scope, and duration of the authorization the client asked for.
This happens even for public client although this is recommended against by RFC6819:
Authorization servers should not allow automatic authorization for public clients. The authorization server may issue an individual client id but should require that all authorizations are approved by the end user. For clients without secrets, this is a countermeasure against the following threat: Impersonation of public client applications.
RPT can be obtained from access token
Impact 1, user can obtain a RPT
Summary: By obtaining his own access token, the end-user can directly obtain a RPT (associated with the client application) from the authorization server and bypass any control (restriction, rate limiting, anomaly detection) which could be implemented by the server-side client application. In particular, the access token can be exposed to the user when using the implicit flow or OpenID connect hybrid flow.
According to the UMA specification, the UMA token request is expected to be protected using the client credentials. This prevents the user (and the user's frontend) to directly obtain the RPT (for server-side UMA clients) and therefore prevents the end-user from abusing the RPT (assuming the server-side client does not send the RPT to the user's frontend). Because Keycloak allows obtaining the RPT without using the client credentials, this type of protection is not possible.
When using the OAuth code exchange or the UMA flow with confidential server-side clients, the access token / RPT is not actually exposed to the user / client frontend (see the BFF pattern) unless the client backend actually passes the RPT to the client frontend (see the Token Mediating pattern):
- This might limit the attacks a malicious user can take on the resource server as the user can only take actions on the resource server through the client backend.
- This might prevent the malicious user from seeing some data which would not be exposed by the backend server to the user frontend.
Scenario:
- the malicious user authenticates himself on the authorization server using the OpenID Connect hybrid flow;
- the malicious user obtains an OpenID Connect access token;
- the malicious user makes a direct request to the authorization server using his OpenID Connect access token in order to obtain a RPT token for accessing some protected resources on RS;
- the malicious attacker can now make direct calls to the RS using this RPT.
In addition, the user could obtain RPTs for resources and resource_scopes
the application would not have tried to obtain.
Mitigation: the can be be fixed by only allowing the code exchange flow which does not expose the OpenID access token to the user.
Impact 2, malicious resource server can obtain a RPT
A malicious resource server RS2 can use an access token (for example a RPT for accessing a resource on RS2) it received from a client application in order to obtain another RPT for accessing the resources on another resource server (RS1):
- This might not be highly problematic if all your resource servers are under your control. Even in this case, this could be used for lateral movement: an attacker taking obtaining control of RS1 could use this position to obtain RPTs for RS2.
- This is very problematic if some resource servers are not under your control.
Conclusion
The UMA implementation in Keycloak appears to work under the assumption that all clients and resource server are highly trusted:
- For example, it seems it is quite a bad idea to use in the same realms both untrusted clients (such as an untrusted OpenID Connect relying party) and UMA resources servers. If you mix these, you are giving access to the UMA resources to the untrusted clients.
- Conversely, if you are using multiple UMA resource servers, each of them will in practice be able to access all the resources of the other resource servers.
Keycloak's documentation should at the very least highlight this assumption and document the behavior described in this post. Without this, people will build broken systems on top of the Keycloak implementation. Event with proper documentation, ths current behavior can be tricky and unexpected and lead to vulnerable systems.
Authorization servers are complex and critical software. Exploring the intricacies of their behavior can lead to interesting vulnerabilities.
References
- UMA 2.0 RPT Request should be authenticated using the client credentials (not the RqP's OIDC access token)
- An analysis of the Keycloak authentication system
- keycloak-tests
Appendix, table of the different results
The following table summarizes different situations for a RPT request when using Keycloak:
- The requested resource has been allocated for resource server 1 (RS1).
- Client1 is another OAuth client.
- Bob is a end-user.
Authentication \ Claim | None | Bob AT on client 1 | Bob ID token on client1 | Bob RT on client 1 |
---|---|---|---|---|
RS1 credentials | 403 | 200 | 200 | 200 |
Client1 credentials | 403 | 200 | 200 | 200 |
Bob AT on client 1 | 200 | 200 | 200 | 200 |
Bob ID token on client 1 | 401 | 401 | 401 | 401 |
Bob RT on client 1 | 401 | 401 | 401 | 401 |
The request is of the form:
POST .../token HTTP/1.1
Authorization: {AUTHENTICATION}
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:uma-ticket
&ticket={TICKET}
&claim_token={CLAIM}
&claim_token_format=http://openid.net/specs/openid-connect-core-1_0.html#IDToken
Some elements were omitted:
- When used as claims, access tokens, refresh tokens and ID tokens produce the same result. This is not true when these token are used as authentication.
Appendix, access token protection
The OAuth 2.0 spec mentions the idea that one might want to avoid exposing the tokens to the user's frontend:
[Implicit Grant] Because the access token is encoded into the redirection URI, it may be exposed to the resource owner and other applications residing on the same device.
[web application profile] The client credentials as well as any access token issued to the client are stored on the web server and are not exposed to or accessible by the resource owner.
The OAuth 2.0 for Browser-Based Apps draft explains that one benefit of the BFF pattern is that it does not expose the tokens to the user / front-end:
The BFF counters the first two payloads by not exposing any tokens to the browser-based application. Even when the attacker gains full control over the JavaScript application, there are simply no tokens to be stolen.
Moreover the BFF can implement rate limiting, anomaly detection:
If a high-security application would prefer to implement anomaly detection or rate limiting, such a BFF would be the ideal place to do so. Such restrictions can further help to mitigate the consequences of client hijacking.
Including UMA RPT's (which are access tokens). But not access tokens and not refresh tokens. ↩︎