Saltar al contenido
>_ITDITDPlataforma de seguridad web

Guías de seguridad

Contaminación de prototipos en Node.js: lo que el runtime no cubre y cómo defenderte en tu propio código

Una clave especial en los datos de entrada, arrastrada por un merge recursivo, hace aparecer propiedades que nunca fijaste en objetos sin relación. Node.js dice que no es una vulnerabilidad del núcleo, así que la defensa es responsabilidad tuya.

Publicado 2026-09-05 Actualizado 2026-10-04 Última verificación 2026-09-05 8 min de lectura

Recibes datos de fuera y, después, empiezan a aparecer en objetos sin relación propiedades que nunca fijaste. O un método estándar como hasOwnProperty de repente «no es una función» y la aplicación se cae. Ambos son síntomas de la contaminación de prototipos (prototype pollution).

Qué cubre Node.js y qué queda en tus manos

Node.js dice abiertamente que no es una vulnerabilidad suya

La guía oficial de seguridad de Node.js lo deja claro: «Según el modelo de amenazas de Node.js, la contaminación de prototipos que depende de que un atacante controle la entrada del usuario no se considera una vulnerabilidad del núcleo de Node.js, porque Node.js confía en las entradas que le pasa el código de la aplicación».

Y justo después: «Aun así, la contaminación de prototipos es una clase grave de vulnerabilidades para las aplicaciones Node.js y las bibliotecas de terceros, y debes implementar defensas a nivel de aplicación y de dependencias».

No es una contradicción; indica dónde está la responsabilidad. El runtime se sitúa del lado que no garantiza que la entrada esté bien formada. Eso significa que «esperar un CVE, esperar una actualización» no funciona aquí. Es otro caso de algo con lo que este sitio se topa una y otra vez: la parte que creías que alguien estaba cubriendo no tiene responsable.

Qué falla (dos consecuencias)

Entrada externa (JSON, query, formulario)

lleva una clave especial

↓ merge recursivo / copia profunda / montaje de configuración

Se reescribe la plantilla compartida (prototipo)

↓ y con ella todo lo que la hereda

1. Aparecen valores que nunca fijaste

se tuercen los indicadores de permiso y las opciones

2. Desaparecen métodos integrados

la aplicación se cae en un punto sin relación (DoS)

Una escritura pensada para un objeto afecta a todo lo que comparte la plantilla. Por eso el daño aparece en un lugar sin relación.
Las dos consecuencias, en términos operativos
1. Inyección de propiedades
Se vuelve legible un valor «que era imposible que estuviera fijado». En cualquier punto donde un objeto de opciones o un indicador de permiso se decide con una simple comprobación de existencia, se da por bueno el valor inyectado. Si llega a una decisión de autorización, es un problema de privilegios; si llega al montaje de plantillas o comandos, puede ser bastante peor
2. Denegación de servicio
Si se rompe la propia plantilla, un método integrado como hasOwnProperty lanza un error «is not a function». La documentación lo presenta como una posible DoS. Lo que conviene recordar: no se trata solo de escalada de privilegios
Hasta dónde llega
Los ejemplos citados son del propio Node.js (CVE-2022-21824) y de una biblioteca de terceros (Lodash, CVE-2018-3721). No escribir tú un merge recursivo no cambia nada si lo escribió una dependencia

Las mitigaciones que enumera la documentación (siete)

Node.js las detalla. Se aplican más fácilmente agrupadas por lo que hacen: frenarlo en la entrada, hacer que la estructura no se pueda contaminar y cambiar la forma de leer.

Frénalo en la entrada

・Evita los merges recursivos inseguros (la documentación cita CVE-2018-16487)
・Implementa validaciones con JSON Schema para peticiones externas y no confiables

No dejar entrar nunca una clave inesperada es la vía más segura. Replantéate los diseños que aceptan un objeto profundo entero y lo fusionan con el estado existente.

Cambia la estructura y las lecturas

・Object.create(null) para crear objetos sin prototipo (ideal para diccionarios)
・Object.freeze(MyObject.prototype) para congelar el prototipo
・La opción --disable-proto de Node para desactivar Object.prototype.proto
・Object.hasOwn(obj, key) para comprobar que la propiedad es propia del objeto
・Evita usar métodos de Object.prototype

El cambio de mayor impacto es cómo compruebas la existencia

En la práctica, lo que más rinde es pasar a Object.hasOwn(obj, key). Una comprobación de existencia simple (¿existe esta propiedad?) responde que sí también para los valores heredados de la plantilla, y así es exactamente como se recoge un valor inyectado. Confirmar que la propiedad es propia del objeto evita por sí solo la mayor parte de la consecuencia 1.

Por la misma razón, evita llamar a obj.hasOwnProperty(key) desde el propio objeto: ese método es una de las cosas que la contaminación puede eliminar (consecuencia 2).

Qué revisar en tu propio código

1

¿Aceptas entradas externas como un objeto profundo?

Cuerpos de petición, query strings, formularios, payloads de webhooks: localiza cada punto que recibe una estructura anidada entera y la fusiona con el estado existente. Es el único sitio por donde entra la entrada externa. Decidir de forma explícita qué claves aceptas, mediante un esquema, es la corrección más corta.

2

Cambia cómo se crean los objetos de tipo diccionario

Todo lo que se use como «un contenedor cuyas claves se deciden en ejecución» (un mapa por ID, un conjunto de ajustes) debe crearse con Object.create(null). Sin plantilla, no hay nada que heredar. Resuelve con la estructura lo que de otro modo tendrías que resolver con validación.

3

Unifica las comprobaciones de existencia con Object.hasOwn

Busca las ramas que dependen de si existe una propiedad y conviértelas. Primero las comprobaciones de permisos, indicadores y opciones: están directamente ligadas a cómo se implementa la autorización.

4

Pon una herramienta a vigilar tus dependencias

No escribir tú un merge recursivo no cambia nada si lo escribió una dependencia; por eso los ejemplos citados incluyen una biblioteca de terceros. Las vulnerabilidades conocidas de las dependencias no se pueden seguir a mano, así que ponlas bajo vigilancia automática como en primeros pasos con osv-scanner. El riesgo más amplio de una dependencia envenenada se trata en defenderse de los ataques a la cadena de suministro de npm.

5

Mira la entrada de tu framework de servidor del mismo modo

Todo framework de Node tiene una vía que deserializa el cuerpo de la petición. Cuando revisamos los datos de NVD, las vulnerabilidades de Next.js se concentraban en las responsabilidades del lado servidor: caché, middleware, Server Actions (¿tienen muchas vulnerabilidades Next.js y React?). Empieza por abandonar la idea de que «es una biblioteca de front-end, así que esto no aplica».

La opinión de este sitio: donde la responsabilidad está escrita, compruébalo tú

Lo más útil a nivel operativo de este artículo no es la lista de mitigaciones, sino la frase que dice que esto no se considera una vulnerabilidad del núcleo de Node.js. Cuando la responsabilidad se declara con tanta claridad, nadie se ocupa de lo que queda fuera: no llegará ningún CVE ni ninguna actualización lo arreglará.

Lo tratamos como el mismo tipo de problema que un certificado que no te protege de una toma de control (toma de control de subdominios) y un bloqueo que no elimina la política existente que tiene debajo (buckets públicos). ¿Quién es realmente responsable de la parte que dabas por cubierta? Responderlo una vez suele reordenar bastante tus prioridades.

Fuentes (primarias)

  • Node.js, «Security Best Practices» (sección Prototype Pollution Attacks / CWE-1321) — nodejs.org (la postura del modelo de amenazas, las dos consecuencias, los CVE citados y las siete mitigaciones proceden de este documento; consultado el 5 de septiembre de 2026)

Para seguir leyendo

FAQ

Q¿La contaminación de prototipos es un bug de Node.js? ¿La arreglará una actualización?
A

No va a llegar ninguna actualización. La guía oficial de seguridad de Node.js afirma que, según el modelo de amenazas de Node.js, la contaminación de prototipos que depende de que un atacante controle la entrada del usuario no se considera una vulnerabilidad del núcleo de Node.js, porque Node.js confía en las entradas que le pasa el código de la aplicación. Y continúa: aun así, la contaminación de prototipos es una clase grave de vulnerabilidades para las aplicaciones Node.js y las bibliotecas de terceros, y debes implementar defensas a nivel de aplicación y de dependencias. Por diseño, es responsabilidad tuya.

Q¿Qué es lo que falla en realidad?
A

Dos consecuencias. Primero, empiezan a aparecer en objetos de todo el proceso propiedades que nunca fijaste: si un indicador de permiso o una opción se decide con una simple comprobación de existencia, se da por bueno el valor inyectado y la autorización o la lógica de negocio se tuercen. Segundo, denegación de servicio: si se rompe el prototipo integrado, un método estándar como hasOwnProperty lanza un error «is not a function» y se cae código que no tiene nada que ver con la entrada. La segunda consecuencia muestra que no se trata solo de escalada de privilegios.

Q¿Dónde lo corrijo?
A

En la entrada y en las estructuras de datos. En la entrada, aplica validación con JSON Schema a las peticiones externas y no confiables para que nunca entren claves inesperadas. En las estructuras, deja de usar merges recursivos inseguros y crea los objetos de tipo diccionario con Object.create(null) para que no tengan prototipo. Al leer, usa Object.hasOwn(obj, key) para confirmar que la propiedad es propia del objeto y evita depender de métodos de Object.prototype. A nivel operativo también existen Object.freeze y la opción --disable-proto de Node.

QNunca escribí un merge recursivo, ¿estoy a salvo?
A

No. Los ejemplos que cita la documentación son tanto del propio Node.js (CVE-2022-21824) como de una biblioteca de terceros (Lodash, CVE-2018-3721). Fusionar configuración, analizar query strings y deserializar formularios son lugares donde bibliotecas muy usadas montan objetos profundos por ti. Así que la defensa no se cierra dentro de tu propio código; va unida a vigilar tus dependencias de forma automática.