Like handing out spray paint to janitors — only people with the Editor key can tag the wall, and someone else has to walk by to get hit
CVE-2025-2703 is a stored DOM-based cross-site scripting flaw in Grafana's built-in XY Chart plugin. The fieldValueColors() function in scatter.ts interpolates threshold step values directly into a dynamically constructed JavaScript function without sanitization. An attacker with Editor permissions (or the fixed:library.panels:general.writer RBAC permission) can inject arbitrary JavaScript into a panel's threshold configuration. When any other user — including an Org Admin — views the poisoned dashboard, the payload fires in their browser session. Affected versions span Grafana 11.1.0 through 11.5.x (pre-patch). Fixed in 11.2.8+security-01, 11.3.5+security-01, 11.4.3+security-01, 11.5.3+security-01, and 11.6.0+security-01.
Grafana rates this Medium (CVSS 6.8) and that assessment is honest. Tenable's legacy CVSS v2 score of 7.3 pushes the plugin into a 'High' label, which is misleading — the v3.1 vector correctly captures the authentication prerequisite (PR:L) and mandatory user interaction (UI:R). The real friction is the Editor role gate: this is not an unauth RCE or even a Viewer-accessible bug. An attacker who already holds Editor credentials has significant access to begin with. The XSS adds a privilege-escalation vector to Admin, but it requires a social-engineering step (the victim must open the dashboard). Sonar's detailed writeup by Paul Gerste provides a near-complete exploitation blueprint, which elevates the risk above a typical stored XSS, but the chain is still fundamentally *auth + click*.
4 steps from start to impact.
Obtain Editor credentials
fixed:library.panels:general.writer RBAC permission. This can come from compromised credentials, insider access, or a misconfigured SSO/LDAP mapping that grants Editor by default.- Valid Grafana account with Editor role
- Network access to the Grafana instance
- Editor is not the default role for new users in well-configured orgs
- SSO integrations typically map to Viewer by default
- Credential compromise is a separate, non-trivial prerequisite
Inject XSS payload into XY Chart threshold
conds += \v >= ${steps[i].value} ? ${i} : \``.- Dashboard with XY Chart panel or ability to create one
- Panel edit access
- XY Chart is a relatively new plugin (v11.1+); not every dashboard uses it
- Library panels with restricted write access limit the blast radius
Lure victim to view the poisoned dashboard
- Victim must navigate to and render the poisoned dashboard
- Victim's browser must execute JavaScript (virtually guaranteed)
- If the dashboard is personal/draft, organic viewership is near zero
- Organizations with per-dashboard RBAC may restrict who can view the panel
- Content Security Policy with Trusted Types blocks the DOM sink entirely
Harvest session token and escalate
- Payload must successfully exfiltrate the token (requires outbound network from victim browser)
- Victim session must have higher privileges than attacker
- Network egress controls or proxy inspection may block the exfiltration callback
- HttpOnly cookie flag on Grafana session cookie (default since Grafana 9.x) blocks
document.cookieaccess — attacker must use alternative techniques like DOM manipulation or API calls from the victim's session context - SameSite cookie attributes limit cross-origin exfiltration paths
The supporting signals.
| In-the-wild exploitation | Not observed. Not listed in CISA KEV. No GreyNoise tags or confirmed campaigns targeting CVE-2025-2703. GreyNoise Grafana activity relates to the older CVE-2021-43798 path traversal. |
|---|---|
| Proof-of-concept | Detailed technical writeup by Paul Gerste (SonarSource) published April 2025 — includes the vulnerable code path, injection point, and fix diff. No standalone weaponized PoC repo found on GitHub as of September 2026, but the writeup is sufficient for a skilled attacker to reproduce. |
| EPSS | 0.1616 (16.2%) — 96.84th percentile. Higher than most CVEs but reflects stored-XSS-class probability, not imminent mass exploitation. |
| CISA KEV | Not listed. No known federal mandate for remediation. |
| CVSS v3.1 vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:L/A:L — Network-accessible, low complexity, but requires low-privilege auth *and* user interaction. Scope is Unchanged (no sandbox escape). Confidentiality impact High reflects session-token theft potential. |
| Affected versions | Grafana 11.1.0 through 11.5.x (before +security-01 patches). The XY Chart plugin was introduced in 11.1.0, so earlier major versions (10.x, 9.x) are not affected. |
| Fixed versions | 11.2.8+security-01, 11.3.5+security-01, 11.4.3+security-01, 11.5.3+security-01, 11.6.0+security-01 and all later releases. |
| Internet exposure | ~600K+ Grafana instances internet-reachable per ZoomEye; ~128K+ per Shodan. Not all run affected 11.1+ versions. Grafana Cloud instances are vendor-managed and patched. |
| Disclosure timeline | 2025-03-14 reported to Grafana → 2025-03-21 acknowledged → 2025-04-15 CVE assigned → 2025-04-22 patch released → 2025-04-24 public disclosure. |
| Credit | Paul Gerste, Vulnerability Researcher at SonarSource. |
Why this verdict
- Editor-role gate compresses the attacker pool. Editor is not the default role. In well-configured orgs with SSO/LDAP, most Grafana users land in Viewer. The attacker needs to already hold a privileged position — this is not an unauth or Viewer-accessible bug.
- Mandatory user interaction adds a social-engineering dependency. The victim must navigate to and render the specific poisoned dashboard panel. This converts a stored XSS from 'fire-and-forget' into a targeted phishing play within the Grafana UI.
- HttpOnly session cookies (default since Grafana 9.x) limit direct token theft. The attacker cannot simply read
document.cookie. They must use more complex in-session DOM manipulation or same-origin API calls, which reduces reliability and increases development cost. - No confirmed in-the-wild exploitation after 17 months. Patch released April 2025; no KEV listing, no GreyNoise tags, no reported campaigns as of September 2026. The EPSS percentile (96.84%) reflects structural similarity to exploited XSS classes, not observed activity.
- Role multiplier: Grafana is NOT a canonical high-value-role component. It is an observability frontend, not an identity provider, hypervisor, CI/CD system, or domain controller. The worst-case blast radius is *Grafana-instance-scoped*: an attacker escalating to Org Admin gains access to data source credentials and dashboard management within that Grafana org. This does not directly translate to domain takeover, fleet compromise, or supply-chain impact. Data source credentials *could* enable lateral movement to production databases, but that is a second-order chain requiring additional exploitation. The floor test is not triggered — Grafana does not occupy the high-value catalog at ≥10% of installs by definition.
- Tenable VPR of 3.9 (Low) corroborates low real-world signal. VPR incorporates threat intelligence feeds and exploitation telemetry, and it places this well below the action threshold.
Why not higher?
Upgrading to HIGH would require either confirmed exploitation, unauth access, or a blast radius extending beyond the Grafana instance. None apply here. The Editor auth gate means the attacker is already a semi-trusted insider. The XSS cannot escape the Grafana web context — it does not provide RCE on the server, kernel access, or domain-level credential harvest. The Scope is Unchanged in the CVSS vector, confirming no sandbox escape.
Why not lower?
Dropping to LOW would ignore the 96.84th-percentile EPSS score, the detailed Sonar exploitation blueprint, and the fact that stored XSS in a *shared monitoring platform* has legitimate privilege-escalation value. If an Editor compromises an Admin session, they inherit data source management — which in many orgs means plaintext database credentials stored in Grafana's configuration. The attack is realistic in environments with lax dashboard sharing and flat Grafana RBAC. It merits scheduled remediation, not backlog burial.
What to do — in priority order.
- Enable Trusted Types in Content Security Policy — Grafana supports a
Content-Security-Policyheader withrequire-trusted-types-for 'script'. This blocks the DOM XSS sink entirely by preventing untrusted strings from being passed toeval-like functions. This is Grafana's own recommended mitigation and should be deployed as the primary compensating control. Per noisgate mitigation SLA for MEDIUM, there is no mitigation deadline — go straight to the 365-day remediation window. However, enabling Trusted Types is low-effort and worth doing immediately. - Audit and restrict Editor role assignments — Review Grafana org role mappings in your SSO/LDAP integration. Ensure new users default to Viewer, not Editor. Revoke Editor from accounts that do not actively need panel-editing capability. This reduces the pool of accounts that could inject a payload.
- Enable and monitor Grafana audit logging — Grafana Enterprise includes audit logging for dashboard changes, panel edits, and data source access. Forward these to your SIEM and alert on non-numeric threshold values in XY Chart panel JSON or unexpected dashboard sharing events to Org Admins.
- Upgrade Grafana to a patched version — Apply the vendor patch: upgrade to 11.2.8+security-01, 11.3.5+security-01, 11.4.3+security-01, 11.5.3+security-01, 11.6.0+security-01, or any later release. Per the noisgate remediation SLA for MEDIUM, complete within 365 days of assessment. Given this CVE is already 17 months old, prioritize in your next maintenance window.
- Restrict outbound network from user browsers via proxy — If your Grafana users access dashboards through a corporate browser or proxy, block outbound requests to unknown domains. This disrupts the exfiltration callback required for session-token theft.
- WAF rules targeting XSS patterns — The payload is stored server-side in Grafana's dashboard JSON configuration, not delivered via a URL parameter or POST body that a WAF inspects. The WAF never sees the injection.
- Network segmentation of the Grafana server — The XSS executes in the *victim's browser*, not on the Grafana server. Isolating the server's network does not prevent client-side JavaScript execution.
- Disabling the XY Chart plugin — The XY Chart is a built-in core plugin in Grafana 11.1+. It cannot be disabled or uninstalled through the standard plugin management interface without modifying Grafana's source or build.
Crowdsourced verification payload.
Run this script from any host with network access to your Grafana instance. It queries the Grafana API health endpoint (unauthenticated) to detect the running version and checks against the affected range. Usage: bash check_cve_2025_2703.sh https://grafana.example.com:3000. No special privileges required — the /api/health endpoint is public by default.
#!/usr/bin/env bash
# CVE-2025-2703 Checker — Grafana XY Chart DOM XSS
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
GRAFANA_URL="${1:-http://localhost:3000}"
GRAFANA_URL="${GRAFANA_URL%/}"
# Attempt version detection via health endpoint
VERSION=$(curl -sf --max-time 10 "${GRAFANA_URL}/api/health" 2>/dev/null \
| grep -oP '"version"\s*:\s*"\K[^"]+' 2>/dev/null || true)
if [ -z "$VERSION" ]; then
VERSION=$(curl -sf --max-time 10 "${GRAFANA_URL}/api/frontend/settings" 2>/dev/null \
| grep -oP '"version"\s*:\s*"\K[^"]+' 2>/dev/null || true)
fi
if [ -z "$VERSION" ]; then
echo "UNKNOWN - Could not determine Grafana version from ${GRAFANA_URL}"
exit 2
fi
echo "Detected Grafana version: ${VERSION}"
# Track security suffix
HAS_SEC=0
[[ "$VERSION" == *"+security"* ]] && HAS_SEC=1
BASE="${VERSION%%+*}"
# semver compare: returns 0 if $1 >= $2
ver_gte() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$2" ]
}
# Below affected range
if ! ver_gte "$BASE" "11.1.0"; then
echo "PATCHED - Version ${VERSION} is below the affected range (< 11.1.0)"
exit 0
fi
# Above all affected branches (11.6.1+ is safe regardless)
if ver_gte "$BASE" "11.6.1"; then
echo "PATCHED - Version ${VERSION} is above all affected branches"
exit 0
fi
# Check each fixed branch
check_branch() {
local fix="$1" ceil="$2"
if ver_gte "$BASE" "$fix" && ! ver_gte "$BASE" "$ceil"; then
if [ "$HAS_SEC" -eq 1 ]; then
echo "PATCHED - Version ${VERSION} includes the security fix"
exit 0
fi
fi
}
check_branch "11.6.0" "11.6.1"
check_branch "11.5.3" "11.6.0"
check_branch "11.4.3" "11.5.0"
check_branch "11.3.5" "11.4.0"
check_branch "11.2.8" "11.3.0"
# If we reach here with a version in [11.1.0, ...) it is vulnerable
echo "VULNERABLE - Version ${VERSION} is affected by CVE-2025-2703 (Grafana XY Chart DOM XSS)"
exit 1- Tenable Plugin 241364
- Grafana Security Advisory — CVE-2025-2703
- Grafana Security Release Blog (CVE-2025-3260, CVE-2025-2703, CVE-2025-3454)
- SonarSource — Data in Danger: Detecting XSS in Grafana
- FIRST.org EPSS API — CVE-2025-2703
- Grafana at Internet Scale — 603K Exposed Instances
- NVD — CVE-2025-2703
- Red Hat CVE Page — CVE-2025-2703
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.