CVE-2016-6582: Doorkeeper fails to revoke OAuth 2.0 public client's access token in revocation request

TL;DR: If a public client respects the RFC 7009 spec and does not authenticate the revocation request, then Doorkeeper does not actually revoke the access token. Upgrade to version 4.1.1, 3.1.1, or 2.2.3

The Problem

Doorkeeper implements OAuth 2.0 access & refresh token revocation (RFC 7009) as of version 1.2.0.

The crux of the problem is, as per RFC 7009 spec, the revocation request MUST contain the parameter token and doesn't require authentication unless it's a confidential client (i.e. a JavaScript-powered Single Page App in the browser). However, Doorkeeper incorrectly implemented the spec and does not proceed unless it is an authenticated/authorized request. As a result Doorkeeper looks for access_token, bearer_token, or an Authorization request header via the Helpers::Controller#doorkeeper_token's delegation to OAuth::Token.

Furthermore, RFC 7009 makes no mention of supporting anything but providing the token parameter so all of the alternative methods for finding the access token via the helper fail to save the day (eg Authorization header's Basic or Bearer values).

Finally, by spec definition-- despite not finding a token to revoke–
Doorkeeper will respond with a 200 empty body. Now this on its own is fairly innocuous but likely "hid" this lack of revocation from developers-- Leaving them none the wiser. At least that was the case with me.

Why Authenticating is Pointless

As I stated before, simply knowing the access token implicitly verifies the request.

The access token is considered a secret which, if stolen via session hijacking, let's an attacker impersonate the user. In other words, the access token represents the trusted user.

Now if we're posting to the revocation endpoint, the idea of authenticating the request in the usual places or authorizing the token parameter matches the authentication is pointless.

If it doesn't match, an attacker can simply make their authentication match that of the token's value.

The very existence of token is sufficient to represent a trusted user and anything more is just fluff– It doesn't add any additional security.

Impact

All public, non-confidential clients respecting the RFC will not have their access or refresh tokens revoked when sending a valid, well-formed & unauthenticated revocation request to doorkeeper.

Any such clients relying on Doorkeeper's revocation functionality are susceptible to a session replay attack, even after the victim terminates their session via a revocation/log out.

  1. Attacker gains access token via any acceptable means (MiTM, physical computer access, bug in client code, etc.)
  2. Victim logs out/attempts to revoke the access token
  3. Attacker is not affected, as the token is still valid for the duration of its lifespan. Furthermore, the refresh token can be used to extend the attacker's privileged access.

This scenario is captured under the OWASP Top 10 (2013)'s A2: Broken
Authentication and Session Management as a vulnerability
.

Now obviously the attacker must already be able to obtain the access token, but without revocation the victim has a false sense of security & cannot limit damage to their account. Additionally, clients relying on the Doorkeeper revocation endpoint to revoke all other issued tokens on password change, password reset, etc. would also be impacted by this problem.

Solution

The true solution is to fully comply with the RFC 7009 and do not authenticate/authorize the request. This means removing reliance on #dookeeper_token and assert presence of the token directly, revoking it if it's present.

Patch/Workaround

See https://github.com/doorkeeper-gem/doorkeeper/commit/fb938051777a3c9cb071e96fc66458f8f615bd53


Header image is by Nick Harris under the Creative Commons license CC BY-ND 2.0