Skip to content

/oauth2/introspect performs no token-ownership check: any authenticated client can introspect any token #4911

Description

@sahandilshan

Description

POST /oauth2/introspect authenticates the caller but never checks whether the presented token
belongs to that caller. Any client holding valid credentials can introspect any token issued
by the deployment, including tokens belonging to other clients and to admin users.

Root cause

backend/internal/oauth/oauth2/introspect/init.go:56-58 correctly wraps the route in
clientauth.ClientAuthMiddleware, so the caller must authenticate. But the handler never reads the
authenticated client back out of the request context — handler.go:47 calls:

h.service.IntrospectToken(ctx, token, tokenTypeHint)

The authenticated client is never passed down, and service.IntrospectToken has no client
parameter, so no ownership comparison is possible anywhere in the call chain.

Reproduction

Two confidential apps A and B, both client_secret_basic + client_credentials. Mint a token for
A, then introspect it while authenticating as B:

POST /oauth2/introspect   -u 'qa-b6-client-B:<secretB>'   -d token=<A's access token>

HTTP 200
{"active":true,"client_id":"qa-b6-client-A","token_type":"Bearer","exp":1786594915,
 "iat":1786591315,"nbf":1786591315,"sub":"019ff923-e7d0-73c5-a137-507823b4e38b",
 "aud":"qa-b6-client-A","iss":"https://localhost:8090","jti":"019ff924-078d-7f28-88a3-f8355678f097"}

The response is byte-identical to A introspecting its own token.

A second demonstration: the same throwaway client successfully introspected the CONSOLE admin
user's
access token, recovering scope: "openid system", sub and jti.

Controls confirm authentication itself is enforced, so ownership is the only gap:

  • unauthenticated → 400 {"error":"invalid_request","error_description":"Missing client_id parameter"}
  • wrong secret → 401 {"error":"invalid_client",...}

Expected

RFC 7662 §2.1 requires the endpoint to determine whether the caller is authorized for the token.
The service should compare the authenticated client against the token's client_id (or its
audience / resource server), returning {"active": false} for tokens the caller does not own.

Impact

Security-relevant. Any client credential in the deployment becomes an oracle for enumerating
metadata about arbitrary tokens — subject identifiers, scopes, audiences, expiry and jti
including privileged admin tokens.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions