/dev/posts/

Keycloak UMA vulnerabilities

Published:

Updated:

Keycloak UMA's implementation seems tricky to me.

Table of content

Summary

Findings:

Tested on:

Overview of Keycloak UMA implementation

Keycloak has an implementation of UMA with a lot of caveats and deviations from the specification:

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.

Accepted combinations for UMA token requests.
Authentication claim_token
client credentials user ID token
client credentials user access token
client credentials user refresh token
user access token none

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:

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):

Scenario:

  1. the malicious user authenticates himself on the authorization server using the OpenID Connect hybrid flow;
  2. the malicious user obtains an OpenID Connect access token;
  3. 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;
  4. 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):

Conclusion

The UMA implementation in Keycloak appears to work under the assumption that all clients and resource server are highly trusted:

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

Appendix, table of the different results

The following table summarizes different situations for a RPT request when using Keycloak:

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:

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.


  1. Including UMA RPT's (which are access tokens). But not access tokens and not refresh tokens. ↩︎