
CVE-2026-82286 — gpt-crawler <=1.5.1 écriture arbitraire de fichier non authentifiée via outputFileName (POST /crawl). PoC + laboratoire Docker autonome. CVSS 8.6, CWE-22.
Preuve de concept pour une écriture arbitraire de fichier non authentifiée dans
BuilderIO/gpt-crawler (≈22k ★) via le
paramètre outputFileName du point de terminaison d'API POST /crawl.
| CVE | CVE-2026-82286 |
| Produit | gpt-crawler (BuilderIO) |
| Versions affectées | <= 1.5.1 (dernière version ; la branche HEAD par défaut est également affectée) |
| Classe | Traversée de chemin / Écriture arbitraire de fichier (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 (ÉLEVÉ) |
| Authentification | Aucune (non authentifiée) |
| Condition préalable | Mode serveur API actif (npm run start:server) |
| Statut | CONFIRMÉ de bout en bout (non corrigé au 2026-08-29) |
Le serveur API (src/server.ts) expose POST /crawl sans aucune authentification. Le corps
JSON est analysé directement dans la Config du crawler ; outputFileName n'est validé que
comme z.string() — il n'y a aucune contrainte de chemin :
// src/config.ts
outputFileName: z.string(),
Dans src/core.ts, write() construit le chemin de sortie directement à partir de cette
chaîne contrôlée par l'attaquant et appelle fs.writeFile() dessus :
// src/core.ts (write)
const nextFileName = (): string =>
`${config.outputFileName.replace(/\.json$/, "")}-${fileCounter}.json`;
...
await writeFile(nextFileNameString, JSON.stringify(currentResults, null, 2));
Un attaquant distant non authentifié contrôle donc le chemin complet d'un fichier écrit
en dehors de la zone de sortie storage/ prévue — via un chemin absolu (/tmp/x,
/home/<user>/x) ou une traversée ../. Le contenu du fichier est le résultat du crawl
[{title,url,html}], dont les champs proviennent d'une page vers laquelle l'attaquant
oriente le crawler (définissez url sur un serveur que vous contrôlez), le contenu est donc
influencé par l'attaquant.
La seule contrainte est le suffixe forcé -<N>.json (le nom se termine toujours par .json) et
l'enveloppe de contenu en tableau JSON — c'est pourquoi cela est évalué comme une écriture
arbitraire de fichier (I:H), et non comme une RCE propre.
python3 exploit.py \
-t http://TARGET:3000 \
-u http://ATTACKER:8081/index.html \ # page dont le contenu atterrit dans le fichier écrit
-o /home/myuser/PWNED # -> écrit /home/myuser/PWNED-1.json
Le point de terminaison reflète le contenu du fichier écrit dans la réponse HTTP. La preuve
empirique est l'apparition du fichier au chemin choisi par l'attaquant sur le système de
fichiers cible (voir 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 # <- écrit en dehors de storage/, non authentifié
Nettoyage : docker rm -f gptc-vuln; pkill -f 'http.server 8081'.
Un attaquant réseau non authentifié peut créer ou écraser des fichiers à des chemins
arbitraires (sous réserve des permissions de l'utilisateur du processus et du suffixe
-<N>.json) avec un contenu partiellement contrôlé par l'attaquant. Selon le déploiement,
cela permet de falsifier des fichiers de configuration/données, d'écraser l'état de
l'application (intégrité/disponibilité) et, sur des cibles qui consomment ensuite un fichier
*.json écrit, peut être enchaîné plus loin.
Le sink n'est accessible qu'en mode serveur API (src/server.ts, démarré via
npm run start:server / le déploiement containerapp), et non en mode CLI par défaut. Le
serveur lie API_HOST (par défaut localhost) ; les déploiements réels qui exposent l'API
le définissent sur 0.0.0.0 (comme le fait l'image containerapp), rendant le point de
terminaison accessible à distance.
outputFileName par rapport à un répertoire de base fixe et rejeter le résultat
s'il en sort (path.resolve(base, name) + vérification du préfixe) ; supprimer .. et les
chemins absolus.POST /crawl.Recherchez les corps de POST /crawl dont outputFileName contient /, \ ou .., et les
fichiers *-1.json apparaissant en dehors du répertoire storage/ du crawler.
Recherche et PoC : Caio Fabrício (BiiTts).