Skip to content

JWT URL-login flow leaks token to data sources through request parameter in proxy requests

Moderate
KristianGrafana published GHSA-5585-m9r5-p86j Apr 27, 2023

Package

Grafana (Grafana)

Affected versions

9.1.0

Patched versions

9.5.0

Description

Summary

Hi team! This is my first attempt at a report on your new bug bounty program (I got an invite after my report leading up to CVE-2022-31130), I hope I am doing this the right way :)

Similar to CVE-2022-31130 and CVE-2022-39201 there is still an auth token leak present in the JWT auth_token query parameter. When using URL LOGIN on a data-source endpoint Grafana is looking at the auth_token parameter for authentication. Even when the parameter is used for authentication to Grafana it is not removed in the proxied rewuest but is instead passed on to the target data source.

This will leak user auth tokens to third-party services (potentially set up with malicious intent) giving any user finding the token full access to Grafana as the victim user.

Details

When setting up Grafana there is an option to enable JWT authentication (see https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/jwt/). Enabling this will allow users to authenticate towards the Grafana instance with a special header (default X-JWT-Assertion).
While patching CVE-2022-31130 there was a check added for the configured JWT header to be removed and not leaked to data-source (see commit grafana/grafana@4dd56e4). This seems to work, but there is another way to authenticate using JWT called URL LOGIN (see https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/jwt/#url-login) where the token is passed as a query parameter. This flow might not be as used, and it already has a warning about it exposing tokens in browser history. But as with the header it should not be sent to data sources, which is currently the case.

This here is the file where the auth token is retrieved from query params
https://github.com/grafana/grafana/blob/faef3a825882a94fbf3f3a30a829176271fce8cf/public/app/core/utils/urlToken.ts

And here is where the function is used and the token converted to an auth header
https://github.com/grafana/grafana/blob/faef3a825882a94fbf3f3a30a829176271fce8cf/public/app/core/services/backend_srv.ts#L133

One easy fix would be to remove the auth_token parameter from the query list when it is consumed bu the authentication flow.

POC

I tested this using a trial auth0.com account but it should work with any form of JWT signing configuration.

Setup Auth0

  1. Create an account on auth0.com to get access to a subdomain such as my https://joaxcar.eu.auth0.com
  2. Log in to your account and go to User Management and create a user (use some made-up email and password)
  3. Go to Applications and click on the Default application (or create a new one)
  4. Take note of the Client ID and the Client secret
  5. Scroll to the bottom and click on Advanced settings and check the box for Password authentication.

Setup Grafana

  1. run this docker command (replace the JWK_SET_URL with your new auth0 d=subdomain)
docker run \
  -d \
  -p 3000:3000 \
  --name grafana_test \
  -e "GF_AUTH_JWT_ENABLED=true" \
  -e "GF_AUTH_JWT_HEADER_NAME=X-JWT-Assertion" \
  -e "GF_AUTH_JWT_EMAIL_CLAIM=sub" \
  -e "GF_AUTH_JWT_USERNAME_CLAIM=sub" \
  -e "GF_AUTH_JWT_JWK_SET_URL=https://joaxcar.eu.auth0.com/.well-known/jwks.json" \
  -e "GF_AUTH_JWT_AUTO_SIGN_UP=true" \
  -e "GF_AUTH_JWT_URL_LOGIN=true" \
  -e "GF_AUTH_JWT_ALLOW_ASSIGN_GRAFANA_ADMIN=true" \
  grafana/grafana-oss:latest
  1. Go to https://localhost:3000 and log in as admin/admin
  2. Configure a Prometheus data source on http://localhost:3001/datasources. Use a "catch server" as the URL. I used burp collaborator, but you can also use a third-party site as webhook.site or a localhost netcat
  3. Save the data source

Get a JWT token

  1. Send a request like this to your auth0 instance (change CLIENT_ID, CLIENT_SECRET, SUBDOMAIN, USERNAME, PASSWORD)
curl -X POST -H "Content-Type: application/json" -d '{
  "client_id": "CLIENT_ID",
  "client_secret": "CLIENT_SECRET",
  "audience": "https://SUBDOMAIN.auth0.com/api/v2",
  "scope": "openid email read:email",
  "username": "USERNAME",
  "password": "PASSWORD",
  "grant_type": "http://auth0.com/oauth/grant-type/password-realm",
  "realm": "Username-Password-Authentication"
}' "https://joaxcar.eu.auth0.com/oauth/token"
  1. save the returned id_token (you can pipe the above request to jq '.["id_token"]' to extract it)

The leak

  1. Now use the new id_token in this request
curl 'http://localhost:3000/api/datasources/proxy/1?auth_token=TOKEN'
  1. Go to the catch server and you should now see a request coming through that contains the JWT in the request parameters.

What Grafana did here was to authenticate the user using the JWT to give the user access to the data source. Then Grafana forwarded the request to the data source leaving the JWT in place.

You can try this request in comparison which will show how the authentication works using headers instead

curl 'http://localhost:3000/api/datasources/proxy/1' -H 'X-JWT-Assertion: TOKEN'

This will also authenticate the user and forward the request, but the auth header will be removed from the proxy request.

Impact

The leaked token gives anyone finding it full access to Grafana as the victim user for as long as the JWT is valid. The JWT can also contain sensitive information in it self as it is an ID_TOKEN.

I think that you are better at assessing the risk here. The CVSS is probably the same as for CVE-2022-31130 (CVSS 6.8 (CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H)) even though this one might be a bit more niche. I think PR (privilege required) is the trickiest one. Looking at it as a pure leak to third-party systems there is no need for any privilege at all for a user with access to one of these third-party systems to gain unauthorized access to Grafana, but to use it maliciously (by for example configuring a malicious data source) an attacker would need high privilege.

Anyhow please get back to me if I need to clarify anything in the report, or If you need me to add you to my aouth0 tenant for ease of testing!

Best regards
Johan

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
High
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:N/A:N

CVE ID

CVE-2023-1387

Weaknesses

Credits