Seguridad en JSON: vulnerabilidades comunes y cómo evitarlas
JSON no lleva código ejecutable y se libra de los riesgos de entidades externas de XML y de las sorpresas de coerción de tipos de YAML, así que tiene fama de ser seguro de analizar. El paso de análisis merece casi toda esa fama. Las vulnerabilidades reales están en lo que tu código hace después con el objeto analizado.
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 proteger las claves especiales, un atacante puede inyectar propiedades en el propio Object.prototype. Eso afecta a cada objeto de tu aplicación, no solo al que recibe la fusión.
{
"__proto__": {
"isAdmin": true
}
}Fusiona esto en profundidad en un objeto de usuario con una fusión recursiva que no trate aparte las claves __proto__, constructor o prototype, y cada objeto del proceso, incluidos los que nunca tocaron esta carga útil, puede acabar con una propiedad isAdmin a true. Los atacantes han explotado esta clase de vulnerabilidad 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 que contaminar. Prefiere Map a los objetos planos cuando las claves vengan de la entrada del usuario y no necesites serializar el resultado a JSON. Si debes fusionar en profundidad JSON no confiable en un objeto existente, usa una utilidad de fusión que bloquee __proto__, constructor y prototype como claves, y comprueba si tu dependencia lo ha corregido (la mayoría de las bibliotecas principales lo han hecho en versiones recientes) en lugar de suponerlo. Mantén las dependencias al día: los mantenedores han corregido esta clase de vulnerabilidad muchas veces en paquetes populares a medida que los investigadores encontraban nuevas formas de saltarse los parches.
Deserialización insegura más allá del JSON.parse plano
El JSON.parse() a secas 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 puede ejecutar código arbitrario al deserializar). El riesgo aparece cuando una función reviver o un paso posterior reconstruye instancias de clases o ejecuta lógica a partir de valores de campos no confiables. Un ejemplo: código que lee un campo "type" del JSON y lo usa para hacer require() de un módulo o despachar a un gestor por nombre sin comprobar la cadena contra una lista de permitidos.
Valida contra un esquema antes de fiarte de JSON no confiable
JSON.parse() garantiza la validez sintáctica y 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 trae una cadena donde esperabas un número, puede tumbar código posterior o producir un comportamiento erróneo sin ningún error. Valida el JSON entrante contra un esquema (JSON Schema, Zod, Joi o similar) antes de usarlo. Capturas la estructura malformada en la frontera, la rechazas con un mensaje de error útil en lugar de un crash confuso tres funciones más abajo, y el esquema documenta la forma que espera el endpoint.
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. No 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 compruebes los valores contra una lista de permitidos. Mantén actualizadas las dependencias que manejan JSON (parsers, utilidades de fusión, validadores de esquemas), porque los investigadores siguen encontrando variantes nuevas de estos bugs. Fija límites de tamaño en los cuerpos de petición JSON para reducir la exposición a ataques de agotamiento de recursos con cargas útiles enormes o muy 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?
- Un atacante envía una entrada (a menudo JSON con una clave __proto__), y código que no protege los nombres de propiedad especiales la fusiona en un objeto. El atacante puede entonces añadir o sobrescribir propiedades en el objeto base compartido del que heredan todos los objetos de JavaScript, lo que afecta a toda la aplicación.
- ¿Necesito validación de esquemas si ya uso TypeScript?
- Sí. El compilador de TypeScript borra los tipos, así que no dan ninguna protección en tiempo de ejecución. Nada comprueba una carga útil JSON malformada o maliciosa que llega por HTTP salvo que la valides 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 puede verificar que los datos entrantes coincidan con esa forma.
- ¿Son YAML o XML más seguros que JSON de analizar?
- No, 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 salvo que configures un modo restringido de "carga segura". Los parsers de XML caen ante ataques de Entidad Externa (XXE) si dejas activada la expansión de entidades. El sistema de tipos más simple de JSON (sin tags, sin entidades) elimina esas superficies de ataque, pero los riesgos de deserialización y fusión tratados aquí siguen vigentes.
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.