← Back to Feed CACHED · 2026-09-22 01:38:32 · CACHE_KEY tenable:241364
tenable:241364 · CWE-79 · Disclosed 2025-04-22

Grafana Labs 11.1.0 < 11.2.8+security-01

ASSESSED — NOISGATE
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

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

"Stored XSS behind Editor auth — real but fenced, not fleet-scale"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Obtain Editor credentials

The attacker must possess a valid Grafana account with Editor role or the 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.
Conditions required:
  • Valid Grafana account with Editor role
  • Network access to the Grafana instance
Where this breaks in practice:
  • 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
Detection/coverage: Failed login attempts visible in Grafana audit log; anomalous Editor account creation detectable via SIEM correlation
STEP 02

Inject XSS payload into XY Chart threshold

The attacker opens or creates a dashboard containing an XY Chart panel. Using the JSON editor or panel configuration UI, they replace a numeric threshold step value with a JavaScript payload. Grafana stores the poisoned configuration server-side without validation. The payload is a string injected into a template literal that becomes executable code: conds += \v >= ${steps[i].value} ? ${i} : \``.
Conditions required:
  • Dashboard with XY Chart panel or ability to create one
  • Panel edit access
Where this breaks in practice:
  • XY Chart is a relatively new plugin (v11.1+); not every dashboard uses it
  • Library panels with restricted write access limit the blast radius
Detection/coverage: Dashboard change audit logs capture panel edits; JSON diff review can flag non-numeric threshold values
STEP 03

Lure victim to view the poisoned dashboard

The stored payload executes whenever *any* authenticated user renders the panel. The attacker waits for organic traffic (shared team dashboards) or actively shares the dashboard link via Slack, email, or Grafana's built-in sharing. Higher-privilege targets (Org Admins) are preferred because their session tokens unlock data source management and user administration.
Conditions required:
  • Victim must navigate to and render the poisoned dashboard
  • Victim's browser must execute JavaScript (virtually guaranteed)
Where this breaks in practice:
  • 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
Detection/coverage: Anomalous dashboard sharing events; CSP violation reports if Trusted Types are enabled
STEP 04

Harvest session token and escalate

The JavaScript payload exfiltrates the victim's Grafana session cookie or API key to an attacker-controlled endpoint. If the victim is an Org Admin, the attacker can now manage data sources (potentially exposing database credentials stored in Grafana), create additional admin accounts, or modify alerting rules. The blast radius is contained to the Grafana instance and its connected data sources.
Conditions required:
  • Payload must successfully exfiltrate the token (requires outbound network from victim browser)
  • Victim session must have higher privileges than attacker
Where this breaks in practice:
  • 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.cookie access — 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
Detection/coverage: Outbound requests to unknown domains from user browsers; Grafana audit log showing admin actions from unexpected IPs/sessions
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNot 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-conceptDetailed 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.
EPSS0.1616 (16.2%) — 96.84th percentile. Higher than most CVEs but reflects stored-XSS-class probability, not imminent mass exploitation.
CISA KEVNot listed. No known federal mandate for remediation.
CVSS v3.1 vectorCVSS: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 versionsGrafana 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 versions11.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 timeline2025-03-14 reported to Grafana → 2025-03-21 acknowledged → 2025-04-15 CVE assigned → 2025-04-22 patch released → 2025-04-24 public disclosure.
CreditPaul Gerste, Vulnerability Researcher at SonarSource.
04 · The Call

Final Verdict
DOWNGRADED to MEDIUM (5.5/10)

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.

05 · Compensating Control

What to do — in priority order.

  1. Enable Trusted Types in Content Security Policy — Grafana supports a Content-Security-Policy header with require-trusted-types-for 'script'. This blocks the DOM XSS sink entirely by preventing untrusted strings from being passed to eval-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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
What doesn't work
  • 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.
06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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
07 · Sources

Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.