Seguridad en JSON: vulnerabilidades comunes y cómo evitarlas
JSON en sí no lleva código ejecutable, a diferencia de XML con sus riesgos de entidades externas o de YAML con sus sorpresas de coerción de tipos — así que tiene fama de ser intrínsecamente seguro de analizar. Esa fama está en gran parte justificada para el paso de análisis en sí, pero lo que tu código hace después con el objeto analizado es donde viven las vulnerabilidades reales.
Contaminación del prototipo vía entrada JSON
Los objetos de JavaScript heredan de una cadena de prototipos compartida. Si un JSON no confiable contiene una clave como "__proto__" y tu código la fusiona en un objeto existente sin protegerse frente a claves especiales, un atacante puede inyectar propiedades en el propio Object.prototype — afectando a cada objeto de tu aplicación, no solo al que se estaba fusionando.
{
"__proto__": {
"isAdmin": true
}
}Si esto se fusiona en profundidad en un objeto de usuario con una fusión recursiva que no trate de forma especial las claves __proto__, constructor o prototype, cada objeto del proceso — incluidos los que nunca tocaron esta carga útil concreta — puede tener de pronto una propiedad isAdmin con valor true. Esta ha sido una clase de vulnerabilidad real en varias bibliotecas populares de merge/extend de npm.
Cómo evitar la contaminación del prototipo
Usa Object.create(null) para los mapas construidos a partir de claves no confiables, ya que no tiene prototipo al que la contaminación pueda llegar. Prefiere Map sobre los objetos planos cuando las claves provengan de entrada del usuario y no necesites serialización JSON del resultado. Si debes fusionar en profundidad JSON no confiable en un objeto existente, usa una utilidad de fusión que bloquee explícitamente __proto__, constructor y prototype como claves — comprueba si tu dependencia lo ha corregido (la mayoría de las bibliotecas principales lo han hecho en versiones recientes) en lugar de asumir que está cubierto. Mantén las dependencias al día: esta clase exacta de vulnerabilidad se ha corregido varias veces en paquetes populares a medida que se descubrían nuevas técnicas de omisión.
Deserialización insegura más allá del JSON.parse plano
El JSON.parse() plano solo produce datos planos — cadenas, números, booleanos, null, objetos planos y arrays. No puede construir instancias de clases arbitrarias ni ejecutar código, a diferencia de otros formatos de serialización (el pickle de Python, por ejemplo, puede ejecutar código arbitrario durante la deserialización). El riesgo aparece cuando una función reviver o un paso aguas abajo reconstruye instancias de clases o ejecuta lógica basada en valores de campos no confiables — por ejemplo, un campo "type" del JSON usado para hacer require() dinámico o despachar a un gestor por nombre de cadena sin validar que la cadena esté en una lista de permitidos.
Valida contra un esquema antes de fiarte de JSON no confiable
JSON.parse() solo garantiza validez sintáctica — no dice nada sobre si los datos coinciden con la forma que tu código espera. Un cuerpo de petición que es JSON válido pero carece de un campo obligatorio, o que trae una cadena donde se esperaba un número, puede romper código aguas abajo o producir silenciosamente un comportamiento erróneo. Validar el JSON entrante contra un esquema (JSON Schema, Zod, Joi o similar) antes de usarlo captura la estructura malformada o inesperada en la frontera, te da un punto de rechazo claro con un mensaje de error útil en lugar de un crash confuso tres funciones más abajo, y además sirve como documentación viva de la forma que el endpoint espera realmente.
Una lista de comprobación práctica
Valida el JSON no confiable contra un esquema en la frontera, antes de que llegue a la lógica de negocio. Nunca fusiones en profundidad JSON no confiable en objetos longevos sin una utilidad de fusión que bloquee las claves peligrosas. Evita funciones reviver o el despacho dinámico basado en campos de cadena no confiables salvo que los valores se comprueben contra una lista de permitidos. Mantén actualizadas las dependencias que gestionan JSON (parsers, utilidades de fusión, validadores de esquemas) — esta es una clase de vulnerabilidad en evolución activa. Establece límites de tamaño razonables en los cuerpos de petición JSON para reducir la exposición a ataques de agotamiento de recursos mediante cargas útiles extremadamente grandes o profundamente anidadas.
FAQ
- ¿Es JSON.parse() vulnerable a la ejecución de código, como eval()?
- No. JSON.parse() solo produce estructuras de datos planas (cadenas, números, booleanos, null, objetos planos, arrays) y nunca ejecuta código, a diferencia del análisis de JSON basado en eval() que algunas bases de código muy antiguas usaban antes de que el JSON.parse() nativo fuera estándar. El JSON.parse() moderno es seguro respecto a la ejecución de código; los riesgos tratados aquí viven en lo que tu código hace después con el resultado analizado.
- ¿Qué es la contaminación del prototipo en términos simples?
- Es una vulnerabilidad donde una entrada controlada por el atacante (a menudo JSON con una clave __proto__) se fusiona en un objeto usando código que no se protege frente a nombres de propiedad especiales, permitiendo al atacante añadir o sobrescribir propiedades en el objeto base compartido del que heredan todos los objetos de JavaScript — afectando a toda la aplicación, no solo a un objeto.
- ¿Necesito validación de esquemas si ya uso TypeScript?
- Sí — los tipos de TypeScript se borran en tiempo de compilación y proporcionan cero protección en tiempo de ejecución. Una carga útil JSON malformada o maliciosa que llega por HTTP no la comprueba nadie en ejecución salvo que la valides explícitamente con una biblioteca como Zod, Joi o un validador de JSON Schema. TypeScript evita que escribas código que maneje mal la forma esperada; no hace nada por verificar que los datos reales coinciden con esa forma.
- ¿Son YAML o XML más seguros que JSON de analizar?
- No intrínsecamente — tienen sus propias clases de vulnerabilidad. Los parsers de YAML en algunos lenguajes admiten coerción de tipos y tags que pueden construir objetos arbitrarios si no se configuran en un modo restringido de "carga segura". Los parsers de XML son vulnerables a ataques de Entidad Externa (XXE) si la expansión de entidades no se deshabilita. El sistema de tipos más simple de JSON (sin tags, sin entidades) elimina esas superficies de ataque concretas, pero los riesgos de deserialización y fusión tratados aquí siguen aplicando.
Prueba estas herramientas
Artículos relacionados
¿Qué es JSON? Una guía completa para principiantes →
JSON (JavaScript Object Notation) es un formato ligero de intercambio de datos, fácil de leer para los humanos y de analizar para las máquinas. Aprende la sintaxis, los tipos de datos y la estructura que impulsan las APIs modernas.
La historia de JSON: del 2000 al estándar de la industria →
Recorre JSON desde la idea de Douglas Crockford en 2001, pasando por su adopción por Yahoo, hasta convertirse en el estándar ECMA-404 que impulsa el 90 % de las APIs modernas.
La guía completa de JSON Schema (Draft 7) →
JSON Schema es el estándar para describir y validar la estructura de JSON. Aprende las palabras clave principales, crea un esquema de API real y aplica buenas prácticas.