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
- Create an account on auth0.com to get access to a subdomain such as my https://joaxcar.eu.auth0.com
- Log in to your account and go to
User Management
and create a user (use some made-up email and password)
- Go to
Applications
and click on the Default application
(or create a new one)
- Take note of the
Client ID
and the Client secret
- Scroll to the bottom and click on
Advanced settings
and check the box for Password
authentication.
Setup Grafana
- 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
- Go to https://localhost:3000 and log in as admin/admin
- 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
- Save the data source
Get a JWT token
- 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"
- save the returned
id_token
(you can pipe the above request to jq '.["id_token"]'
to extract it)
The leak
- Now use the new
id_token
in this request
curl 'http://localhost:3000/api/datasources/proxy/1?auth_token=TOKEN'
- 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
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 usingURL LOGIN
on a data-source endpoint Grafana is looking at theauth_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
User Management
and create a user (use some made-up email and password)Applications
and click on theDefault application
(or create a new one)Client ID
and theClient secret
Advanced settings
and check the box forPassword
authentication.Setup Grafana
Get a JWT token
id_token
(you can pipe the above request tojq '.["id_token"]'
to extract it)The leak
id_token
in this requestWhat 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
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