Saltar al contenido
Portada » Blog – Laprovittera Carlos » OWASP TOP 10 A08:2025 Fallos de integridad en software y datos

OWASP TOP 10 A08:2025 Fallos de integridad en software y datos

En este capítulo veremos: OWASP TOP 10 A08:2025 Fallos de integridad en software y datos Software and Data Integrity Failures. En donde te adentrarás en uno de los pilares más importantes de la seguridad moderna: el OWASP Top 10, la referencia global que define los riesgos más críticos para las aplicaciones web. Aprenderás cómo ha evolucionado esta lista desde 2003 hasta 2025, por qué sigue siendo indispensable para desarrolladores, analistas de seguridad y pentesters, y cómo cada categoría refleja los fallos que se explotan en el mundo real.

Además, profundizaremos en una de las categorías más peligrosas y estratégicas del OWASP Top 10 2025: A08 – Fallos en la Integridad del Software y de los Datos, entendiendo cómo piensa un atacante, cómo explota estos fallos y cuáles son las defensas modernas para prevenirlos. También estudiarás técnicas avanzadas de seguridad del navegador —CSP, Trusted Types, aislamiento de recursos, encabezados críticos, mitigación de XSS y CSRF— todo desde una perspectiva ofensiva y defensiva para que aprendas no solo qué proteger, sino cómo y por qué.

Al finalizar, dominarás los conceptos, mecanismos, riesgos y controles necesarios para fortalecer cualquier arquitectura web frente a los ataques más comunes y sofisticados de la actualidad.

Table of Contents

OWASP TOP 10 

OWASP es popular por publicar el TOP 10 Owasp Web cada cuatro años. Este es un  documento que lista los diez riesgos más críticos en aplicaciones web, con el objetivo de  ayudar a las organizaciones a identificar y mitigar las vulnerabilidades asociadas con estos riesgos. https://owasp.org/Top10/es/ Cada uno de estos riesgos representa una debilidad común y significativa que a menudo se puede explotar para comprometer la seguridad de una aplicación web. OWASP proporciona información detallada sobre posibles vulnerabilidades y técnicas de ataque para cada riesgo.

El Top 10 de OWASP es una lista que se actualiza periódicamente de los riesgos de seguridad de las aplicaciones web más críticos. Lo mantiene el Proyecto Abierto de Seguridad de Aplicaciones Web (OWASP), una organización sin fines de lucro centrada en mejorar la seguridad de las aplicaciones web. Sirve como una valiosa guía para que los desarrolladores, los expertos en pruebas de penetración de aplicaciones web y las organizaciones comprendan y prioricen los riesgos de seguridad comunes en las aplicaciones web.

El Top 10 de OWASP es una lista conocida de los diez riesgos de seguridad de aplicaciones web más críticos. Se actualiza periódicamente para garantizar que refleje el panorama actual de amenazas y los desafíos de seguridad en constante evolución que enfrentan las aplicaciones web. La primera versión del Top 10 de OWASP se publicó en 2003. Su objetivo era crear conciencia sobre los riesgos comunes de seguridad de las aplicaciones web y ayudar a los desarrolladores a priorizar los esfuerzos de seguridad. La lista incluía riesgos como secuencias de comandos entre sitios (XSS), inyección de SQL y problemas de gestión de sesiones. Cada lanzamiento del Top 10 de OWASP se basa en las versiones anteriores, mejorando su precisión, relevancia y practicidad.

Evolución de OWASP TOP 10

En el momento de crear este artículo coexiste las dos versiones 2021/2025.

⚪No cambia

🟡Fusionado

🔵Cambia de posición

🟢Nuevo

OWASP TOP 10  2025

A08:2025 Fallas de integridad de software o datos

Las fallas de integridad de software o datos continúan en el puesto n.° 8, con un ligero cambio de nombre, aclaratorio, de «Fallas de integridad de software y datos». Esta categoría se centra en la incapacidad de mantener los límites de confianza y verificar la integridad del software, el código y los artefactos de datos a un nivel inferior al de las fallas de la cadena de suministro de software. Esta categoría se centra en realizar suposiciones relacionadas con actualizaciones de software y datos críticos, sin verificar su integridad. Entre las Enumeraciones de Debilidades Comunes (CWE) más destacadas se incluyen CWE-829: Inclusión de funcionalidad de una esfera de control no confiable , CWE-915: Modificación controlada incorrectamente de atributos de objetos determinados dinámicamente y CWE-502: Deserialización de datos no confiables .

Tabla de puntuación.

CWE mapeadosTasa máxima de incidenciaTasa de incidencia promedioCobertura máximaCobertura promedioExploit ponderado promedioImpacto ponderado promedioTotal de ocurrenciasTotal de CVEs
148,98%2,75%78,52%45,49%7.114.79501.3273.331

Descripción.

Las fallas de integridad de software y datos se relacionan con código e infraestructura que no protegen contra código o datos no válidos o no confiables que se consideran confiables y válidos. Un ejemplo de esto es cuando una aplicación depende de complementos, bibliotecas o módulos de fuentes, repositorios y redes de entrega de contenido (CDN) no confiables. Una canalización de CI/CD insegura que no consume ni proporciona comprobaciones de integridad de software puede generar acceso no autorizado, código inseguro o malicioso, o comprometer el sistema.

Otro ejemplo de esto es una CI/CD que extrae código o artefactos de ubicaciones no confiables o no los verifica antes de su uso (mediante la comprobación de la firma o un mecanismo similar). Por último, muchas aplicaciones ahora incluyen la función de actualización automática, donde las actualizaciones se descargan sin una verificación de integridad suficiente y se aplican a la aplicación que antes era confiable. Los atacantes podrían potencialmente cargar sus propias actualizaciones para que se distribuyan y ejecuten en todas las instalaciones. Otro ejemplo es cuando los objetos o datos se codifican o serializan en una estructura que un atacante puede ver y modificar, lo que es vulnerable a la deserialización insegura.

Cómo prevenir.

  • Utilice firmas digitales o mecanismos similares para verificar que el software o los datos provienen de la fuente esperada y no han sido alterados.
  • Asegúrese de que las bibliotecas y dependencias, como npm o Maven, solo utilicen repositorios de confianza. Si su perfil de riesgo es mayor, considere alojar un repositorio interno de confianza y verificado.
  • Asegúrese de que exista un proceso de revisión de los cambios de código y configuración para minimizar la posibilidad de que se introduzcan códigos o configuraciones maliciosos en su flujo de trabajo de software.
  • Asegúrese de que su canalización de CI/CD tenga la segregación, la configuración y el control de acceso adecuados para garantizar la integridad del código que fluye a través de los procesos de compilación e implementación.
  • Asegúrese de que no se reciban datos serializados sin firmar o sin cifrar de clientes no confiables y que posteriormente se utilicen sin algún tipo de verificación de integridad o firma digital para detectar alteraciones o reproducción de los datos serializados.

Ejemplos de escenarios de ataque.

Escenario n.° 1: Inclusión de funcionalidad web de una fuente no confiable: Una empresa utiliza un proveedor de servicios externo para brindar soporte. Para mayor comodidad, cuenta con una asignación DNS para [ myCompany.SupportProvider.comnombre del dominio support.myCompany.com]. Esto significa que todas las cookies, incluidas las de autenticación, configuradas en el myCompany.comdominio se enviarán al proveedor de soporte. Cualquiera con acceso a la infraestructura del proveedor de soporte puede robar las cookies de todos los usuarios que hayan visitado su sitio support.myCompany.comy realizar un ataque de secuestro de sesión.

Escenario n.° 2: Actualización sin firma: Muchos routers domésticos, decodificadores, firmware de dispositivos y otros no verifican las actualizaciones mediante firmware firmado. El firmware sin firmar es un objetivo cada vez mayor para los atacantes y se prevé que empeore. Esto es una gran preocupación, ya que muchas veces no hay otra solución que corregirlo en una versión futura y esperar a que las versiones anteriores queden obsoletas.

Escenario n.° 2: Un desarrollador tiene problemas para encontrar la versión actualizada del paquete que busca, por lo que la descarga en lugar del gestor de paquetes habitual y de confianza, sino de un sitio web. El paquete no está firmado, por lo que no es posible garantizar su integridad. El paquete incluye código malicioso.

Escenario n.° 3: Deserialización insegura: Una aplicación React invoca un conjunto de microservicios de Spring Boot. Como programadores funcionales, intentaron garantizar la inmutabilidad de su código. La solución que idearon fue serializar el estado del usuario y transferirlo con cada solicitud. Un atacante detecta la firma del objeto Java «rO0» (en base64) y utiliza el escáner de deserialización de Java para ejecutar código remoto en el servidor de aplicaciones.

A08:2021 – Fallas en el Software y en la Integridad de los Datos

CWEs mapeadasTasa de incidencia máxTasa de incidencia promExplotabilidad ponderada promImpacto ponderado promCobertura máxCobertura promIncidencias totalesTotal CVEs
1016.67%2.05%6.947.9475.04%45.35%47,9721,152

Una nueva categoría en la versión 2021 que se centra en hacer suposiciones relacionadas con las actualizaciones de software, los datos críticos y los pipelines de CI/CD sin verificación de integridad. Corresponde a uno de los mayores impactos según los sistemas de ponderación de vulnerabilidades (CVE/CVSS, siglas en inglés para Common Vulnerability and Exposures/Common Vulnerability Scoring System). Entre estos, se destacan las siguientes CWEs: CWE-829: Inclusión de funcionalidades provenientes de fuera de la zona de confianzaCWE-494: Ausencia de verificación de integridad en el código descargado, y CWE-502: Deserialización de datos no confiables.

C8: Aprovechar las funciones de seguridad del navegador

Los navegadores son la puerta de entrada a la web para la mayoría de los usuarios. Por ello, es fundamental implementar medidas de seguridad robustas para proteger al usuario de diversas amenazas. Esta sección describe las técnicas y políticas que se pueden implementar para reforzar la seguridad del navegador.

Aunque actualmente nos centramos en los navegadores web tradicionales, tenga en cuenta que existe una amplia gama de programas cliente, desde aplicaciones móviles y clientes API hasta televisores inteligentes. Le recomendamos verificar qué funciones de seguridad del lado del cliente son compatibles con su cliente y utilizar los encabezados HTTP correspondientes para configurarlas.

Seguridad oportunista y compatibilidad con navegadores

Ordenar al navegador web que aplique medidas de seguridad siempre es oportunista: la aplicación web no puede verificar que el navegador respete las instrucciones proporcionadas y, por lo tanto, estas medidas de seguridad siempre deben verse como medidas de refuerzo adicionales (y opcionales) que complican aún más la vida de un atacante.

Además, los navegadores web deben ser compatibles con las directrices de seguridad que ofrecen las aplicaciones web. El nivel de compatibilidad varía según el navegador y su versión. Sitios web como https://caniuse.com permiten comprobar qué navegador (o versiones) son compatibles con cada función. Las funciones de seguridad compatibles pueden cambiar con el tiempo; por ejemplo, el encabezado X-XSS-Protection se ha eliminado de los principales navegadores; el comportamiento predeterminado de los navegadores puede cambiar con el tiempo, como ocurre con Referrer-Policy; e incluso la semántica de los encabezados existentes puede cambiar con el tiempo, como ocurre con X-Content-Type-Options.

Si bien el cambio de funciones del navegador puede ser problemático, los navegadores más recientes suelen ofrecer más funciones de seguridad. A veces incluso las habilitan por defecto. Configurar explícitamente estos encabezados de seguridad puede unificar el comportamiento de los diferentes navegadores y, por lo tanto, reducir el mantenimiento.

Es posible que un navegador totalmente comprometido no tenga en cuenta las instrucciones de seguridad, pero si un adversario pudiera tomar el control total de un navegador, ya tendría caminos de ataque mucho más dañinos que simplemente ignorar las instrucciones de seguridad.

Amenazas

  • Un atacante podría ejecutar ataques de secuencias de comandos entre sitios (XSS) explotando configuraciones inadecuadas de la Política de seguridad de contenido, inyectando potencialmente secuencias de comandos maliciosas en páginas web.
  • Un atacante podría realizar ataques de clickjacking aprovechando los encabezados X-Frame-Options faltantes, engañando potencialmente a los usuarios para que interactúen no deseadas con elementos web disfrazados.
  • Un atacante podría recopilar información confidencial a través de Refererencabezados HTTP cuando no se configura la política de referencia adecuada, lo que podría exponer datos privados o actividades del usuario.
  • Un atacante podría explotar vulnerabilidades de confusión de tipos MIME en ausencia de encabezados X-Content-Type-Options, ejecutando potencialmente scripts maliciosos disfrazados de tipos de archivos benignos.
  • Un atacante podría secuestrar sesiones de usuario explotando configuraciones de cookies inseguras, obteniendo potencialmente acceso no autorizado a las cuentas de usuario.
  • Un atacante podría realizar ataques de revinculación de DNS en ausencia de una fijación de DNS adecuada, eludiendo potencialmente las restricciones de la política del mismo origen.
  • Un atacante podría aprovechar una configuración incorrecta del uso compartido de recursos de origen cruzado (CORS) para obtener acceso no autorizado a los recursos, lo que podría comprometer la confidencialidad e integridad de los datos.

Implementación

Normalmente, hay dos formas (específicas del encabezado de seguridad) que las aplicaciones web pueden utilizar para instruir a los navegadores web acerca de la seguridad: encabezados HTTP y etiquetas HTML.

El comportamiento que se toma si una directiva de seguridad se da más de una vez depende del encabezado de seguridad. Por ejemplo, un encabezado X-Frame-Options duplicado deshabilitará su protección, mientras que un encabezado Content-Security-Policy duplicado generará una política más estricta, reforzando así su seguridad. A continuación, se presenta una lista no exhaustiva de posibles mecanismos de endurecimiento:

Configurar el navegador para evitar la divulgación de información

La divulgación de información ocurre si el navegador transmite información a través de canales no cifrados (HTTP en lugar de HTTPS) o envía demasiada información desde el principio (por ejemplo, a través del encabezado de referencia). Los siguientes mecanismos reducen la posibilidad de divulgación de información:

  • Seguridad de transporte estricta HTTP (HSTS) : garantiza que los navegadores solo se conecten a su sitio web a través de HTTPS, lo que evita ataques de eliminación de SSL.
  • Política de Seguridad de Contenido (CSP) : Las políticas CSP pueden indicar al navegador que actualice automáticamente las conexiones HTTP a HTTPS. Además, directivas como la directiva ‘form-src’ pueden usarse para evitar que los formularios transmitan datos a sitios externos.
  • Política de referencia : al navegar entre páginas, la solicitud HTTP del navegador incluye la URL actual en la solicitud saliente. Esta URL puede contener información confidencial. Mediante la Política de referencia, un sitio web puede unificar el comportamiento del navegador y seleccionar qué información debe transmitirse entre sitios web.
  • Indicador de seguridad de la cookie : aunque no es un encabezado HTTP, este indicador de seguridad está relacionado con la divulgación de información. Si se activa, el navegador web no transmitirá una cookie a través de transportes HTTP sin cifrar.

Reducir el impacto potencial de XSS

Los ataques XSS basados ​​en JavaScript han sido muy comunes durante décadas. Para reducir el impacto potencial de las vulnerabilidades, los navegadores ofrecen mecanismos de defensa sofisticados que deberían reducir el impacto potencial de los ataques XSS:

  • Política de Seguridad de Contenido (CSP) : La CSP es una herramienta potente que ayuda a prevenir una amplia gama de ataques, incluyendo secuencias de comandos entre sitios (XSS) e inyección de datos. Las políticas CSP estrictas pueden deshabilitar eficazmente JavaScript y estilos en línea, lo que dificulta considerablemente la inyección de contenido malicioso por parte de los atacantes.

  • Lista de Hosts Permitidos de la CSP : El bloqueo de todo JavaScript de terceros puede reducir significativamente la superficie de ataque y evitar la explotación de vulnerabilidades en bibliotecas de terceros.

  • CSP Estricta : Una CSP que utiliza nonces o hashes en la directiva ‘script-src’ (a menudo denominada «CSP estricta») proporciona una mitigación robusta contra vulnerabilidades XSS. Opcionalmente, el uso de la palabra clave «strict-dynamic» de la CSP puede ayudar a agilizar la implementación de una CSP estricta y a garantizar la compatibilidad con bibliotecas JavaScript de terceros cuando sea necesario. Tipos de Confianza : Esta es una API del navegador que ayuda a prevenir vulnerabilidades de secuencias de comandos entre sitios basadas en DOM, garantizando que solo se puedan insertar tipos de datos seguros en el DOM.
  • El indicador httpOnly de la cookie : aunque no es un encabezado HTTP, configurarlo impide que JavaScript acceda a esta cookie, por lo que debe hacerse especialmente para las cookies de sesión.

Prevenir el secuestro de clics

El clickjacking, también conocido como ataques de corrección de la interfaz de usuario, intenta confundir a los usuarios superponiendo un sitio malicioso a uno benigno. El usuario cree interactuar con el benigno, mientras que en realidad interactúa con el malicioso.

  • X-Frame-Options (XFO) : Previene ataques de clickjacking al garantizar que su contenido no se incruste en otros sitios. Este encabezado es difícil de usar; por ejemplo, se desactiva si se usa dos veces.
  • Política de seguridad de contenido (CSP) : las diferentes directivas frame-* permiten un control detallado de qué sitios pueden incluir el sitio web actual, así como qué otros sitios pueden incluirse dentro del sitio web actual.

Controlar las funciones avanzadas del navegador

Los navegadores modernos no solo muestran código HTML, sino que también se utilizan para interactuar con múltiples componentes del sistema, como cámaras web, micrófonos, dispositivos USB, etc. Si bien muchos sitios web no utilizan esas funciones, los atacantes pueden abusar de ellas.

  • Política de permisos : mediante una política de permisos, un sitio web puede indicar al navegador que no utilizará las funciones definidas. Por ejemplo, un sitio web puede indicar que nunca capturará el audio del usuario. Incluso si un atacante logra inyectar código malicioso, no podrá indicar al navegador que capture el audio.

Prevenir ataques CSRF

Los ataques CSRF abusan de una relación de confianza existente entre el navegador web y los sitios web.

  • Cookies del mismo origen: marcar las cookies como SameSite puede mitigar el riesgo de fuga de información de origen cruzado, así como brindar cierta protección contra ataques de falsificación de solicitud entre sitios.
  • Encabezados de solicitud de obtención de metadatos: marcar los encabezados de solicitud de obtención de metadatos en el lado del servidor permite implementar un sólido mecanismo de defensa en profundidad (una política de aislamiento de recursos) para proteger su aplicación contra ataques comunes de origen cruzado como CSRF.

Vulnerabilidades prevenidas

La implementación de estas defensas del navegador puede ayudar a mitigar una variedad de vulnerabilidades, que incluyen, entre otras:

  • Secuencias de comandos entre sitios (XSS)
  • Falsificación de solicitud entre sitios (CSRF)
  • Secuestro de clics
  • Robo de datos mediante transmisión insegura
  • Secuestro de sesión
  • Abusar del acceso no intencionado al hardware del navegador (micrófono, cámaras, etc.)

Fallas de Integridad como un Hacker

Cuando hablamos de integridad en ciberseguridad no estamos hablando de valores morales. Estamos hablando de una sola cosa: garantizar que algo no cambió. Que lo que descargaste, lo que almacenaste o lo que ejecutás sea exactamente lo que esperás. Porque si no lo es, entonces alguien lo tocó. Y si alguien lo tocó y vos no lo detectaste, ya no tenés el control. Y si no tenés el control, tenés una brecha. Punto.

Pensalo desde el punto de vista ofensivo. ¿Querés comprometer una organización? No necesitás explotar un 0-day. A veces alcanza con meter una línea de código en una biblioteca que la empresa baja automáticamente. O reemplazar un archivo en tránsito porque no usan HTTPS. O meter un payload persistente en un token JWT mal firmado. Todo eso es posible cuando no hay controles de integridad. Y es mucho más común de lo que parece.

Empecemos por lo básico: los hashes. Toda verificación de integridad parte de ahí. Si descargás un binario y te ofrecen un SHA256 para comprobarlo, usalo. No es opcional. Un atacante con un MITM o acceso a un CDN vulnerable podría darte un archivo comprometido, y si no verificás, ni te enterás. ¿La solución? Hash calculado y comparado localmente. Ejemplo clásico: descargás WinSCP, chequeás con sha256sum, y si el hash coincide con el publicado, dormís tranquilo. Si no coincide, tenés un problema.

Tomemos WinSCP como ejemplo para comprender mejor cómo podemos usar hashes para comprobar la integridad de un archivo.  Si visita su repositorio de Sourceforge , verá que, por cada archivo disponible para descargar, hay varios hashes publicados:

Estos hashes fueron precalculados por los creadores de WinSCP para que puedas comprobar la integridad del archivo tras descargarlo. Si descargamos el WinSCP-5.21.5-Setup.exearchivo, podemos recalcularlos y compararlos con los publicados en Sourceforge. Para calcular los diferentes hashes en Linux , podemos usar los siguientes comandos:

Pero eso es solo una capa. Porque en el mundo real no estás descargando todo desde sitios oficiales. Muchas veces estás usando dependencias externas, librerías que cargás en tiempo real desde CDNs que no controlás. El ejemplo con jQuery es brutal: si tu HTML carga https://code.jquery.com/jquery-3.6.1.min.js y alguien mete código malicioso en ese archivo, cada visitante tuyo lo va a ejecutar. Bienvenido al infierno del Supply Chain XSS. Lo irónico es que los navegadores modernos te dan herramientas para evitar esto, como SRI (Subresource Integrity), donde podés pegar un hash en el HTML para que el navegador bloquee la carga si el archivo fue modificado. Pero si no lo usás, es como dejar la puerta abierta con una nota que dice “no tocar”.

Y esto no es paranoia. Esto pasa. Y cuando pasa, el atacante no necesita tocar tu servidor, solo comprometer un eslabón de la cadena que vos usás sin control. ¿Cómo se mitiga? Controlando las dependencias, congelando versiones, auditando los hashes, y usando SRI en todo lo que venga desde afuera.

Ahora hablemos de integridad de datos. Ese es el otro flanco. Supongamos que tenés una app web que mantiene sesiones con cookies. ¿Qué pasa si en lugar de usar tokens firmados, guardás un valor como user=susan en texto plano? El navegador va a reenviar esa cookie en cada request, y nada impide que un atacante modifique el valor y se convierta en user=admin. ¿Resultado? Privilegios escalados sin esfuerzo. ¿Solución? Tokens firmados. Pero ojo: no cualquier firma. Firmas con clave secreta y algoritmos seguros.

Acá entran en juego los JWT (JSON Web Tokens). Son ideales para eso porque te permiten encapsular datos clave-valor, firmados digitalmente. ¿Cómo funciona? Tres partes: header, payload, y firma. El header te dice qué algoritmo se usó (por ejemplo, HS256), el payload guarda los datos (como user=admin), y la firma se genera aplicando el algoritmo con una clave secreta. Si alguien modifica el payload sin tener la clave, no puede generar una firma válida. Simple, elegante y efectivo. Pero como todo, mal implementado es una trampa mortal.

Cuando un usuario navega a su sitio web, su navegador leerá su código HTML y descargará jQuery desde la fuente externa especificada.

JWT y el algoritmo None

Hace tiempo, se presentó una vulnerabilidad de integridad de datos en algunas bibliotecas que implementaban JWT. Como hemos visto, JWT implementa una firma para validar la integridad de los datos de la carga útil. Las bibliotecas vulnerables permitieron a los atacantes eludir la validación de la firma modificando los dos siguientes elementos en un JWT:

  1. Modifique la sección de encabezado del token para que el alg encabezado contenga el valor none.
  2. Retire la parte de la firma.

Tomando el JWT anterior como ejemplo, si quisiéramos cambiar la carga útil para que el nombre de usuario sea «admin» sin verificación de firma, tendríamos que decodificar el encabezado y la carga útil, modificarlos según sea necesario y volver a codificarlos. Observe cómo eliminamos la parte de la firma, pero conservamos el punto al final.

¿Querés una joya del pasado? El algoritmo «none». Algunas librerías JWT permitían declarar alg=none en el header, lo que básicamente le decía al sistema que no firmaste nada. Y lo aceptaban. ¿Resultado? Un atacante podía modificar el token, sacarle la firma y el sistema lo validaba igual. Porque “confío en vos”. Hoy suena absurdo, pero todavía hay sistemas que lo tienen activo sin saberlo.

Otro error común es confiar en la estructura de un JWT sin validar la firma. Recordá que los JWT están en base64, no cifrados. Cualquiera puede decodificarlos. Si los usás sin verificación, estás regalando acceso. Y si la clave de firma está expuesta (o es predecible), peor todavía. La integridad muere en el momento en que alguien más puede fabricar un token válido sin pasar por vos.

Cómo el Software y los Datos se Vuelven tu Peor Enemigo si Dormís

Todo esto se agrava en aplicaciones modernas que usan bibliotecas de terceros para integrarse con el navegador. Si usás librerías que manipulan el DOM sin control, te abrís a ataques como DOM XSS. ¿La solución? Trusted Types, una implementación que redefine cómo se inyecta HTML dinámico y bloquea directamente los puntos de inyección si no pasan por una política segura. Es un enfoque nuevo, pero poderoso: en lugar de limpiar después de inyectar, evita que se inyecte lo que no está permitido. Y lo mejor: podés implementarlo incluso si el navegador no lo soporta, con polyfills. Si hacés bien el trabajo, tu app ya es más segura sin depender del soporte del cliente.

¿Querés ir más allá? Sumale una política de Content Security Policy (CSP) con hashes y nonces. CSP te permite declarar desde qué orígenes podés cargar recursos, bloquear inline scripts, deshabilitar eval() y más. Si lo combinás con Trusted Types, tenés un entorno que previene XSS antes de que empiece. Pero atención: CSP mal configurado no sirve. Muchos creen que con script-src ‘self’ ya están protegidos, pero si dejás habilitado unsafe-inline o no definís nonces, seguís abierto. Y si no sabés qué cargar y desde dónde, te lo vas a comer vos.

Para cerrar, volvamos al mindset hacker: las fallas de integridad no son errores de principiante. Son errores de confianza mal puesta. Confiás en que los datos son lo que dicen ser. Confiás en que el software que descargás no fue alterado. Confiás en que tu frontend no va a recibir código nuevo sin que te enteres. Pero un atacante vive para encontrar esos lugares donde confiás sin verificar. Y cuando los encuentra, ya no importa qué firewall tenés o cuántas auditorías hiciste. Ya estás comprometido.

Blindar la integridad en software y datos no es difícil. Pero requiere disciplina. Requiere entender que cada dato que procesás, cada archivo que usás, cada token que validás puede ser el punto de entrada si no tenés control sobre su integridad. Si no lo validás vos, lo va a validar el atacante. Y cuando lo valide, va a saber exactamente cómo explotarlo. No le des esa oportunidad.

Aislar Recursos como un Hacker

 Si tu aplicación expone recursos en la web y no los tenés blindados, entonces los tenés compartidos. Y si los tenés compartidos, tenés un problema. Porque la web moderna está diseñada para interoperar, pero los atacantes también lo saben. CSRF, XSSI, Spectre, fugas de metadatos, canales laterales, sincronización maliciosa… todo parte de lo mismo: tu servidor no tiene forma de saber si la solicitud que recibió proviene de un contexto de confianza. A menos que se la des. Y ahí es donde entran los encabezados de metadatos de recuperación.

Hablemos claro. Cargar una imagen, un JSON o un script desde un sitio malicioso no es ciencia ficción. Es lo que pasa cuando un atacante quiere extraer datos de tu backend, abusar de tus endpoints, o provocar efectos secundarios sin interacción del usuario. Es el terreno donde vive CSRF. La vieja técnica de falsificar una solicitud desde otro sitio y que el navegador la ejecute como si viniera del usuario legítimo. ¿Por qué? Porque las cookies se mandan igual. Y si vos no verificás de dónde viene la solicitud, la procesás igual. Y si la procesás, ya es tarde.

Cómo Cortar los Tentáculos de los Ataques entre Sitios Antes de que Toquen tu App

Ahora bien, los encabezados como Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest y Sec-Fetch-User te permiten mirar dentro de esa solicitud y entender el contexto en el que se generó. Es como tener metadatos sobre la intención del navegador. Y si usás esa información, podés rechazar todo lo que no venga de tu propia app. Así de simple.

¿Querés dejar de comerte XSSI por cargar un JSON desde evil.example? Rechazá todas las solicitudes que no tengan Sec-Fetch-Site como same-origin, same-site o none. ¿Querés evitar que un iframe se robe tu contenido? Validá que el Sec-Fetch-Mode sea navigate y que el destino no sea object ni embed. ¿Querés permitir solo tráfico limpio? Rechazá todo lo que no pase tu lógica.

Y sí, esto es compatible con todos los navegadores modernos. No estás rompiendo nada. Estás blindando.

Same-Origin

Las solicitudes que se originen en sitios que publique tu propio servidor (mismo origen) seguirán funcionando. 

Entre sitios

El servidor puede rechazar las solicitudes entre sitios maliciosas debido al contexto adicional en la solicitud HTTP que proporcionan los encabezados Sec-Fetch-*. 

 

«Mismo sitio» y «Mismo origen»

Los términos «mismo sitio» y «mismo origen» se citan con frecuencia, pero a menudo se malinterpretan. Por ejemplo, se usan en el contexto de las transiciones de página, las solicitudes fetch(), las cookies, la apertura de ventanas emergentes, los recursos incorporados y los iframes. En esta página, se explica qué son y en qué se diferencian entre sí.

Origen

La estructura de un origen.

«Origen» es una combinación de un esquema (también conocido como protocolo, por ejemplo, HTTP o HTTPS), un nombre de host y un puerto (si se especifica). Por ejemplo, con una URL https://www.example.com:443/foo, el «origen» es https://www.example.com:443.

«Del mismo origen» y «de origen diferente»

Los sitios web que tienen la misma combinación de esquema, nombre de host y puerto se consideran «del mismo origen». Todo lo demás se considera «multiorigen».

Origen AOrigen B¿Es de «mismo origen» o «origen cruzado»?
https://www.example.com:443https://www.evil.com:443Origen cruzado: diferentes dominios
 https://example.com:443Origen cruzado: subdominios diferentes
 https://login.example.com:443Origen cruzado: subdominios diferentes
 http://www.example.com:443Origen cruzado: esquemas diferentes
 https://www.example.com:80Origen cruzado: puertos diferentes
 https://www.example.com:443Mismo origen: concordancia exacta
 https://www.example.comMismo origen: El número de puerto implícito (443) coincide

Sitio

Punto clave: La especificación de «sitio» incluye el esquema. Esto es así desde finales de 2019. Antes de que «sitio» incluyera esquemas, se usaba comúnmente «mismo sitio con esquema». Ahora, el «mismo sitio con esquema» se conoce como «mismo sitio», y la forma anterior de mismo sitio se llama «mismo sitio sin esquema». Para obtener más información, consulta «Sin esquema del mismo sitio».

Son las partes de una URL que conforman un sitio.

Los dominios de nivel superior (TLD), como .com y .org, se enumeran en la base de datos de la zona raíz. En el ejemplo

anterior, «sitio» es una combinación del esquema, el TLD y la parte del dominio justo antes de él (lo llamamos TLD+1). Por ejemplo, con una URL https://www.example.com:443/foo, el «sitio» es https://example.com.

Lista de sufijos públicos y eTLD

En el caso de los dominios con elementos como .co.jp o .github.io, usar solo .jp o .io no es lo suficientemente específico como para identificar el «sitio». No hay forma de determinar de forma algorítmica el nivel de dominios registrables para un TLD en particular. Para ayudar con eso, la Lista de sufijos públicos define una lista de sufijos públicos, también llamados TLD efectivos (eTLD). La lista de eTLD se mantiene en publicsuffix.org/list.

Para identificar la parte «sitio» de un dominio que incluye un TLD internacional, aplica la misma práctica que en el ejemplo con .com. Tomando https://www.project.github.io:443/foo como ejemplo, el esquema es https, el eTLD es .github.io y el eTLD+1 es project.github.io, por lo que https://project.github.io se considera el «sitio» para esta URL.

Las partes de una URL con un TLD electrónico.

«del mismo sitio» y «entre sitios»

Los sitios web que tienen el mismo esquema y el mismo eTLD+1 se consideran «mismo sitio». Los sitios web que tienen un esquema o un eTLD+1 diferentes son «multisitios».

Origen AOrigen B¿Es «del mismo sitio» o «en varios sitios»?
https://www.example.com:443https://www.evil.com:443Entre sitios: diferentes dominios
 https://login.example.com:443Mismo sitio: Los diferentes subdominios no importan.
 http://www.example.com:443Entre sitios: diferentes esquemas
 https://www.example.com:80En el mismo sitio: No importa que los puertos sean diferentes.
 https://www.example.com:443Mismo sitio: concordancia exacta
 https://www.example.comEn el mismo sitio: los puertos no importan

«Sin esquema del mismo sitio»

La definición de «mismo sitio» cambió para incluir el esquema de URL como parte del sitio para evitar que se use HTTP como un canal débil. El concepto anterior de «mismo sitio» sin comparación de esquemas ahora se denomina «mismo sitio sin esquema». Por ejemplo, http://www.example.com y https://www.example.com se consideran del mismo sitio sin esquema, pero no del mismo sitio, ya que solo importa la parte del eTLD+1 y no se considera el esquema.

Origen AOrigen B¿Es «sin esquema del mismo sitio» o «entre sitios»?
https://www.example.com:443https://www.evil.com:443Entre sitios: diferentes dominios
 https://login.example.com:443Sin esquema de mismo sitio: Los diferentes subdominios no importan
 http://www.example.com:443Sin esquema de Same-Site: Los diferentes esquemas no importan
 https://www.example.com:80Sin esquema del mismo sitio: No importa que los puertos sean diferentes.
 https://www.example.com:443Sin esquema del mismo sitio: concordancia exacta
 https://www.example.comSin esquema del mismo sitio: los puertos no importan

Cómo verificar si una solicitud es «del mismo sitio», «del mismo origen» o «entre sitios»

Todos los navegadores modernos envían solicitudes con un encabezado HTTP Sec-Fetch-Site. El encabezado tiene uno de los siguientes valores:

  • cross-site
  • same-site (se refiere a la misma ubicación con esquema)
  • same-origin
  • none

Puedes examinar el valor de Sec-Fetch-Site para determinar si la solicitud es del mismo sitio, del mismo origen o entre sitios.

Puedes confiar razonablemente en el valor del encabezado Sec-Fetch-Site porque:

Punto clave: Incluso si un cliente HTTP aleatorio envía al servidor un valor manipulado para el encabezado Sec-Fetch-Site, no se perjudica a ningún usuario o navegador si se incumple la política del mismo origen.

Clickjacking

Ahora pasemos a clickjacking. Ese clásico donde tu sitio está embebido en un iframe invisible, y el usuario hace clic pensando que está tocando otra cosa. Resultado: activaste sin querer una transacción, un botón de eliminar o cualquier acción peligrosa. Para evitar esto, tenés múltiples capas. Lo primero es usar encabezados como X-Frame-Options: DENY o mejor aún, Content-Security-Policy: frame-ancestors ‘none’. Pero no te confíes solo en uno: combiná todo.

Y no, no pongas esto en un <meta> tag. Los navegadores lo ignoran. Tiene que ser un header HTTP real. ¿Querés defensa en profundidad? Sumá el atributo SameSite a tus cookies. Así no se mandan en requests embebidos desde otros sitios. ¿El navegador no soporta SameSite? Bueno, hay un 6% que todavía está en 2020, pero vos no podés darte el lujo de esperarlos. SameSite no es perfecto, pero es un refuerzo más.

Y para los casos extremos, sí, todavía existe el viejo framebuster en JS. No es bonito, no es elegante, pero puede frenar ataques en navegadores viejos que no respetan headers modernos. Eso sí: no uses los scripts basura como if (top != self) top.location = self.location. Se rompen con doble iframe, con políticas sandbox, con diseño heredado. El atacante ya leyó ese truco hace años. Hay técnicas mucho más finas, como usar window.confirm() para mostrar el origen real, o inyectar onbeforeunload para bloquear redirecciones. Son hacks, sí, pero en seguridad real, los hacks bien pensados salvan.

Por último, tené presente esto: si tenés recursos que sí deben ser accesibles desde otros orígenes (como una API pública o una imagen), eximilos explícitamente de tu política de aislamiento. No los dejes colgando. Controlalos.

Implementar un aislamiento de recursos efectivo no es una tarea opcional. Es una necesidad. Te permite definir una política que diga: “yo solo respondo a lo que venga de mí mismo”. Y todo lo que venga de afuera, lo parás antes de que llegue a tocar lógica sensible. Es firewalling lógico basado en intención. Y si no lo hacés, vas a estar jugando al ping-pong con solicitudes de sitios que ni sabés que existen, pero que conocen cada rincón expuesto de tu app.

La web es un lugar hostil. Y vos tenés que decidir si tu servidor responde a cualquiera o solo a quienes querés. Elegí bien. Porque si no lo filtrás vos, lo va a aprovechar otro.

Suplantación de Identidad como un Hacker

El phishing es uno de esos ataques que no necesitan romper tu sistema para hacerte pedazos. Basta con que alguien se haga pasar por vos, por tu marca, por tu sitio, y ya empieza el desastre. Porque a diferencia de las estafas clásicas, el phishing es más sutil: en lugar de empujar una mentira en tu cara, simula la verdad con precisión quirúrgica. Y cuando lo hace bien, ni tu cliente ni tus defensas se dan cuenta. El daño no se mide solo en datos robados o dinero perdido. Se mide en confianza erosionada, en reputación destrozada, en usuarios que nunca más vuelven. Y eso, para una marca digital, es el principio del fin.

El problema es que muchos desarrolladores todavía piensan que el phishing es culpa del usuario. “Si el cliente cae en un correo trucho, ¿qué querés que haga?”. Esa mentalidad no solo es ingenua, es peligrosa. Porque vos podés no ser el origen del ataque, pero vas a ser el nombre que aparece en la estafa. Y cuando explote, el cliente va a pensar que fuiste vos. Entonces, si querés tomarte en serio la seguridad de tu aplicación, tenés que entender que el phishing también es tu problema. Y tenés que pelearlo como un hacker: desde la ingeniería social hasta los encabezados SMTP.

Todo empieza con la identidad. El atacante necesita parecer legítimo. Para eso, falsifica correos, URLs, dominios, formularios. Se disfraza de vos. Copia tu marca, tu lenguaje, tu diseño. Y lo hace tan bien que cuando el usuario recibe ese mail urgente diciendo “tu cuenta fue comprometida, hacé clic acá”, lo cree. El enlace lleva a un sitio idéntico al tuyo, pero falso. El usuario escribe sus datos, y listo. Cuentas comprometidas, sesiones robadas, fraude en tiempo real. A veces ni siquiera hay interacción directa: software espía se mete en el navegador y pesca los datos al vuelo, o scripts maliciosos se montan sobre redireccionadores tuyos mal protegidos. Todo suma al mismo infierno: el robo silencioso de confianza.

La primera línea de defensa es técnica, y empieza por los protocolos que deberían haber sido estándar hace diez años: SPF, DKIM y DMARC. Si no tenés un registro SPF en tu DNS, cualquiera puede mandar un mail “desde” tu dominio. Si no firmás tus correos con DKIM, nadie puede saber si fueron modificados. Si no publicás políticas DMARC, nadie sabe qué hacer cuando los mails fallan. Y si encima mandás tus campañas desde servidores random de proveedores externos, con dominios que no coinciden con el tuyo, les estás haciendo el trabajo a los phishers. Firmá tus correos. Usá tus propios dominios. Asegurate de que todo lo que sale de tu sistema sea trazable. Y si podés, firmalo digitalmente con S/MIME.

Pero eso no alcanza. El usuario es el blanco final del ataque, y si no lo preparás, va a caer. La educación es clave. Tenés que repetirle hasta el hartazgo que nunca le vas a pedir secretos por mail. Que nunca le vas a mandar enlaces para hacer clic. Que si quiere entrar a su cuenta, que escriba tu URL a mano. Y que si recibe algo raro, que te lo reenvíe. Tené una casilla dedicada a eso. Reaccioná rápido. Mostrá que te importa. Porque cada minuto que esa estafa circula es un minuto más en el que tu nombre se quema.

¿Qué es CSP?

Un Content-Security-Policy es un encabezado HTTP de respuesta que te permite controlar qué recursos (scripts, estilos, imágenes, etc.) puede cargar y ejecutar el navegador. Sirve para mitigar XSS, clickjacking, data injection y otros ataques del lado del cliente. CSP es un estándar de seguridad web — definido por el W3C — que permite a los sitios web indicarle al navegador qué recursos están permitidos en una página (scripts, estilos, imágenes, fuentes, iframes, etc.).

  • Se aplica mediante una cabecera HTTP Content-Security-Policy (o, en casos limitados, mediante una etiqueta <meta> en el HTML).
  • Con CSP estamos implementando, del lado del cliente, el principio de “mínimo privilegio”: en lugar de asumir que todo contenido externo es confiable, solo permitimos explícitamente lo que sabemos que debe cargarse. Todo lo demás se bloquea.

Sintaxis básica

Content-Security-Policy: <directiva> <valores>; <directiva> <valores>; …

Ejemplo:

Content-Security-Policy: default-src ‘self’; script-src ‘self’ https://cdn.example.com;

Content Security Policy como un HackerT

Te digo algo que no muchos desarrolladores quieren escuchar: si tu app web no tiene CSP, estás en pelotas. Estás navegando Internet como si no existieran los XSS, como si los navegadores fueran guardianes angelicales que jamás ejecutarían código malicioso. Pero la verdad es otra: si vos no le ponés límites explícitos al navegador, él va a asumir que todo vale. Va a ejecutar cualquier <script> que vea, cualquier <img> que se cargue, cualquier iframe embebido, cualquier inline-style. Y eso no es un problema del browser, es un problema tuyo. Por eso existe CSP: para darte la chance de que construyas una política de confianza. Un cerco. Un firewall del lado del cliente.

CSP no es un feature, es una declaración de guerra. Le estás diciendo al navegador: «Confío solo en lo que yo defino, todo lo demás es un ataque». Y esa mentalidad es exactamente la que necesitás si querés blindar tu frontend. Porque no importa cuán bien sanitices la entrada de datos, o cuánto escapes los caracteres peligrosos: en algún momento, algo se te puede escapar. Pero con una buena política CSP, incluso si el atacante mete una carga maliciosa en el DOM, el navegador no la va a ejecutar. Porque la política se lo prohíbe. Y ahí ganaste sin tirar una línea de código extra.

La lógica de CSP es brutalmente simple: le decís al navegador qué tipos de recursos puede cargar y desde dónde. Punto. Scripts, estilos, imágenes, fuentes, conexiones, formularios, iframes, media, workers: todo puede ser restringido por origen. Todo puede ser limitado a ‘self’, o a ciertos dominios, o incluso bloqueado completamente con ‘none’. Podés obligar a que todo venga por HTTPS, impedir que nadie te embeber en un <iframe>, bloquear formularios que apunten a sitios externos, o incluso usar hashes y nonces para permitir solo ciertos scripts inline que vos autorizás explícitamente.

Cómo Moldear un Muro Invisible entre Vos y el Caos

Ahora, la clave no es solo tirar una política y listo. La clave es diseñarla como si fueras un atacante que la va a intentar romper. ¿Usás librerías externas? Bien. ¿Sabés exactamente desde dónde se cargan? ¿Sabés si alguna mete scripts inline? ¿Sabés si tenés algún <style> embebido que puede romper la política? ¿Hay algún widget que inyecta contenido dinámico en el DOM? ¿Hay algún endpoint de tu backend que devuelve JSON y podría ser abusado como XSSI? ¿Tenés algún legacy que todavía usa eval() o document.write()? Cada uno de esos puntos puede ser una brecha en tu política CSP si no lo controlás.

La peor trampa en CSP es poner excepciones porque «algo deja de funcionar». Y sí, claro que deja de funcionar. Esa es la gracia. Una política CSP agresiva rompe cosas. Pero rompe las cosas inseguras. Si ves que algo se rompe, no deberías preguntarte «cómo lo dejo pasar», sino «¿por qué eso se rompe con CSP? ¿Qué está haciendo que no debería?». Porque si un script solo funciona si le das ‘unsafe-inline’, probablemente no deberías estar usándolo. ‘unsafe-inline’ es básicamente rendirse. Es decir: “confío en cualquier cosa inline que aparezca en el HTML”. Es regalarle la ejecución a cualquier payload XSS que se filtre.

Y si necesitás hacer algo inline, usá nonces o hashes. Con hashes, podés decirle al navegador exactamente qué contenido inline es aceptable. Si ese bloque de código cambia, el hash no matchea y no se ejecuta. Con nonces, podés inyectar un valor aleatorio en cada carga de página y solo los scripts o estilos que tengan ese nonce se ejecutan. Es como darle un pase VIP a un bloque de código. Sin ese pase, se queda afuera. Pero ojo: los nonces tienen que ser únicos, impredecibles y generados por un RNG criptográficamente seguro. Si son estáticos, es peor que no tener CSP. Porque si alguien roba ese valor, tiene vía libre.

Después está el tema de los reportes. CSP no solo bloquea, también puede reportar. Podés configurar una URL donde el navegador envíe informes cuando alguien intenta violar tu política. Y eso es oro puro. Porque esos logs te permiten ver ataques en tiempo real. Podés ver intentos de XSS, de inyecciones, de iframes externos, de cargas desde CDNs no autorizados. No tenés que esperar a que explote. Lo ves venir. Eso sí, para que funcione, tenés que usar report-uri (aunque va a quedar obsoleto en favor de report-to) y asegurarte de que tu endpoint de reportes acepte los datos y los procese correctamente.

Trusted Types

¿Querés subir el nivel? Combiná CSP con Trusted Types. Eso ya es jugar en ligas mayores. Con Trusted Types, evitás que el código JS del cliente inyecte valores peligrosos en el DOM. Le decís al navegador: «No permitas que nadie meta HTML, URLs o scripts a menos que venga envuelto en un tipo confiable». Y esos tipos confiables solo los podés generar vos, desde tu código, usando APIs específicas. Con eso, se terminó el innerHTML = userInput. Se acabó el element.setAttribute(‘src’, input). Todo tiene que pasar por una validación explícita. Y si no, se bloquea. CSP + Trusted Types es la dupla mortal contra XSS moderno.

Y te tiro una más: si usás Angular, la cosa se pone interesante. Angular ya hace un montón de saneamiento por su cuenta, pero no es magia. Si usás bypassSecurityTrust*, te estás saltando sus defensas. Y si encima tenés una política CSP floja, estás frito. Angular permite configurar nonces con ngCspNonce, o podés usar el token CSP_NONCE para inyectarlo desde tu backend. Si combinás eso con el compilador AOT y evitás el JIT (que necesita excepciones extra en la política), podés llegar a tener una política realmente estricta sin romper nada. Pero hay que planearlo. Porque CSP no se agrega al final. Se diseña desde el principio.

En producción, la política CSP ideal es: todo bloqueado por defecto (default-src ‘none’), solo se permiten los orígenes específicos que vos controlás, nada inline sin hash o nonce, todos los scripts externos declarados explícitamente, nada de eval(), nada de frames externos, todas las conexiones a APIs restringidas a tu dominio, y todo bajo HTTPS. Agregá además upgrade-insecure-requests para forzar HTTPS, y block-all-mixed-content para que no se cuele nada por HTTP.

¿Querés saber si tu CSP sirve o es solo un placebo? Andá a https://csp-evaluator.withgoogle.com. Pegá tu política. Y mirá si te dice “CSP can be bypassed”. Si sí, entonces no es una política, es una ilusión de seguridad.

En resumen: Content Security Policy no es opcional. No es “algo que se pone si te queda tiempo”. Es una de las pocas defensas del lado del navegador que vos controlás. No podés dejarla afuera. Porque sin CSP, cualquier payload inyectado es potencialmente ejecutable. Con CSP, incluso si algo se cuela, tenés una última línea de defensa. Y si lo hacés bien, esa línea es impenetrable.

Blindar tu frontend sin CSP es como construir una bóveda de acero… y dejar la puerta abierta. Así que cerrala. Y cerrala bien. Porque el atacante ya está mirando. Solo está esperando que te olvides de poner ese header. Y cuando eso pase, te va a entrar por el DOM.

¿Para qué sirve? Principales amenazas mitigadas

Las dos principales amenazas contra las cuales CSP ofrece protección son:

  • CrossSite Scripting (XSS) — inyección de scripts maliciosos en páginas web, que permiten robo de cookies, secuestro de sesiones, ejecución de código arbitrario en el navegador, etc.
  • Clickjacking / “framings” maliciosos — páginas maliciosas que embeben tu sitio dentro de un <iframe> para engañar al usuario (hacer que haga “clic” creyendo que está en tu sitio, etc.). CSP permite controlar qué dominios pueden embebernos.

Además, CSP ayuda a mitigar en general riesgos de inyección de contenido (scripts, estilos, objetos, multimedia, etc.) — cualquier recurso cargado desde un origen no permitido puede ser bloqueado.

¿Cómo funciona CSP? Principios y directivas

Cuando tu servidor responde con una cabecera Content-Security-Policy, le indica al navegador una “lista blanca” de orígenes y tipos de recursos permitidos. Si algo no está permitido por la política, simplemente no se ejecuta o no se carga.

Directivas más comunes

  • default-src: origen por defecto — actúa como “fallback” para otras directivas no especificadas.
  • script-src: define de dónde puede cargarse JavaScript (scripts externos, inline, event‑handlers, eval, etc.).
  • style-src: para CSS / estilos — controlar de dónde pueden venir las hojas de estilo o estilos inline.
  • img-src: para imágenes.
  • frame-ancestors: define qué dominios pueden embeber la página en un <frame> o <iframe> — útil contra clickjacking.
  • connect-src, media-src, font-src, object-src, worker-src, manifest-src, etc.: directivas para controlar conexiones AJAX/fetch, multimedia, fuentes, objetos (plugins), web workers, manifiestos de aplicaciones, etc.
  • form-action: define a qué destinos puede enviarse un formulario. Esto puede ayudar a prevenir envíos de formularios hacia sitios externos maliciosos.

La ventana de alerta «XSS» no aparece. ¿Por qué? La respuesta se encuentra en la consola del navegador. El CSP de Hackademy previene el ataque porque no permite scripts en línea como este.

Cómo usar CSP correctamente: buenas prácticas y matices

Para que CSP sea realmente eficaz, debe implementarse cuidadosamente. Algunas recomendaciones:

✔️ Empezar con una política “restrictiva por defecto”

Un ejemplo robusto de CSP básica segura:

Content-Security-Policy:
  default-src ‘none’;
  script-src ‘self’;
  style-src ‘self’;
  img-src ‘self’;
  connect-src ‘self’;
  frame-ancestors ‘none’;
  form-action ‘self’;

Esto significa: no permitir nada salvo lo que venga del mismo origen — scripts, estilos, imágenes, conexiones, formularios — y prohibir cualquier intento de enmarcar la página o cargar objetos externos.

✔️ Evitá (si podés) unsafe-inline y unsafe-eval

  • La palabra clave ‘unsafe-inline’ permite ejecución de scripts inline y event‑handlers (onclick, onerror, etc.). Si la incluyés, estás dejando la puerta abierta a XSS, y en muchos casos anulás gran parte de la protección.
  • En su lugar, si necesitás permitir scripts inline, usá hashes (SHA‑256 of the inline script) o nonces — esto permite mantener el control sobre qué scripts están permitidos, sin dejarlo abierto a cualquiera.

✔️ Considerar “CSP estricto” usando hashes o nonces

Cuando configurás políticas CSP que usan nonces (un valor aleatorio por carga de página) o hashes, el navegador solo ejecutará scripts o estilos que coincidan con esos valores — lo que hace muy difícil para un atacante inyectar un script malicioso que sea aceptado.

✔️ Usá frame-ancestors ‘none’ si no necesitas iframes

Si tu sitio no tiene por qué ser embebido en ningún otro sitio, definir frame-ancestors ‘none’ es una defensa fuerte contra clickjacking.

✔️ Implementá CSP desde el servidor (o configurar el header globalmente)

La cabecera CSP debe enviarse desde el servidor — idealmente de forma global — no como <meta> en HTML cuando usás directivas como frame-ancestors, porque esas directivas no funcionan vía <meta>.

✔️ Usá “modo reporte” antes de aplicar cambios agresivos

Al principio podés usar Content-Security-Policy-Report-Only, lo que hace que el navegador no bloquee, sino report e intentos de violación. Así podés monitorear sin romper funcionalidades. Luego, cuando estés seguro, pasá a modo “enforce”.

✔️ Complementá CSP con buena sanitización de input / output

CSP no reemplaza las buenas prácticas de saneamiento de entrada, escaping, validación de datos, codificación de salida, validación del lado del servidor, etc. Es una defensa en profundidad. Incluso con CSP, si tu backend genera HTML vulnerable, el riesgo persiste.

Limitaciones y aspectos a tener en cuenta

  • CSP no protege contra todo: por ejemplo, exfiltración de datos mediante redirección de ventanas o parámetros URL no siempre se puede bloquear con CSP.
  • Si usás bibliotecas externas, iframes, scripts de terceros (analytics, tags, widgets) — puede complicar mucho la política CSP. A veces requerirá ajustes, uso de nonces/hashes o excepciones.
  • Navegadores antiguos pueden tener soporte parcial o ignorar ciertas directivas — hay que considerar compatibilidad.
  • Certificarse de que los nonces son únicos por carga: repetir nonces o usar nonces predecibles reduce significativamente la seguridad.

Directivas más comunes

DirectivaQué controlaHereda de
default-srcValor por defecto para todas las otras directivas
script-srcJavaScript, WebAssemblydefault-src
style-srcCSS, estilos en líneadefault-src
img-srcImágenes y faviconsdefault-src
font-srcFuentes webdefault-src
connect-srcAJAX, WebSocket, fetch, EventSourcedefault-src
frame-srciframes, framesdefault-src
child-srciframes, web workersdefault-src
object-src<object>, <embed>default-src
form-actionEnvíos de formularios
frame-ancestorsQuién puede embeber tu sitio
media-src<audio>, <video>, <track>default-src
worker-srcWeb Workers, Service Workerschild-src
manifest-srcWeb App Manifestdefault-src

Palabras clave útiles

Palabra claveDescripción
‘self’Permite el mismo origen del documento
‘none’Bloquea todos los recursos del tipo
‘unsafe-inline’Permite código en línea (⚠️ peligroso)
‘unsafe-eval’Permite eval(), Function(), etc. (⚠️ peligroso)
‘strict-dynamic’Scripts cargados por scripts de confianza también son válidos
‘nonce-xyz’Permite scripts/styles inline con ese nonce específico
‘sha256-xxx’Permite contenido inline si su hash coincide
‘report-sample’Adjunta ejemplo del código bloqueado en el informe
‘unsafe-hashes’Permite handlers inline si coinciden con un hash

Políticas de seguridad recomendadas

Política segura mínima

Content-Security-Policy: default-src ‘none’; script-src ‘self’; style-src ‘self’; object-src ‘none’; base-uri ‘none’; form-action ‘self’;

Reescribe HTTP a HTTPS

upgrade-insecure-requests

Bloquea contenido mixto (obsoleto pero aún útil)

block-all-mixed-content

Lo que NO deberías usar (a menos que sepas lo que hacés)

PeligrosoPor qué evitarlo
‘unsafe-inline’Permite ejecutar XSS directamente
‘unsafe-eval’Permite ejecutar código dinámico malicioso
object-src *Permite Flash, Java, PDF embebidos, etc.

Cómo probar CSP sin romper producción

Usá el encabezado Report-Only para monitorear sin aplicar la política:

Content-Security-Policy-Report-Only: default-src ‘self’; report-uri /csp-violation-report;

Y asegurate de tener un endpoint que reciba los reportes:

Reporting-Endpoints: csp-endpoint=»https://tuservidor.com/csp-report»

Herramientas útiles

Tips de experto

  • No uses * como wildcard sin un propósito justificado.
  • Evita unsafe-inline: si necesitás inline, usá nonces o hashes.
  • Siempre probá en Report-Only primero antes de aplicar en producción.
  • Implementá HSTS y CSP juntos para una seguridad fuerte del lado cliente.
  • Considerá Trusted Types en apps SPA o altamente dinámicas (Angular, React, etc.).
  • Rotá los nonces por request, no uses nonces estáticos.

Ejemplo completo de CSP bien estructurada

Content-Security-Policy:
  default-src ‘none’;
  script-src ‘self’ https://cdn.example.com ‘nonce-abc123’;
  style-src ‘self’ https://fonts.googleapis.com;
  font-src https://fonts.gstatic.com;
  img-src ‘self’ https://images.example.com;
  connect-src ‘self’ https://api.example.com;
  form-action ‘self’;
  frame-ancestors ‘none’;
  base-uri ‘none’;
  upgrade-insecure-requests;
  report-uri https://example.com/csp-report;
  report-to csp-endpoint;

Errores comunes

  • 🚫 Olvidarse de definir base-uri y permitir ataques de redirección.
  • 🚫 No bloquear object-src, dejando puerta abierta a exploits antiguos.
  • 🚫 Dejar default-src * y confiar que no habrá XSS.

Integración con frameworks

FrameworkConsideración
AngularUsá CSP_NONCE, evita JIT, preferí AOT.
ReactEvitá dangerouslySetInnerHTML, sanitizá props.
VueUsa v-html solo si sabés lo que hacés.
Next.jsPodés setear CSP en el next.config.js.

Por ejemplo, el CSP a continuación establece dos directivas de búsqueda:

  • default-srcse le da la expresión de fuente única’self’
  • img-srcSe dan dos expresiones fuente: ‘self’yexample.com

Mitigando XSS como un Hacker

La secuencia de comandos entre sitios (XSS) sigue siendo uno de los vectores de ataque más rentables y explotados en aplicaciones web. A pesar de años de educación, linters, frameworks modernos y escáneres automatizados, la realidad es clara: todavía se filtran scripts maliciosos y los navegadores los ejecutan como si nada. Pero si querés jugar en serio, tenés que operar como los que construyen defensas sólidas, no como los que apagan incendios. Y ahí entra la Content Security Policy (CSP). No como una sugerencia, sino como un pilar.

Una CSP estricta, bien implementada, hace que el navegador se convierta en tu último bastión. Aunque el atacante logre meter código dentro del HTML, no puede ejecutarlo. Esa es la clave. Y para eso, hay que ir más allá de las listas de origen y meterse de lleno en el uso de nonces y hashes. Porque si todavía usás script-src ‘self’ www.googleapis.com pensás que estás blindado, te aviso que eso es papel higiénico contra un misil.

Un CSP basada en nonce implica que el servidor genera un valor aleatorio en cada respuesta HTTP, lo embebe en el header Content-Security-Policy, y luego lo aplica como atributo nonce a cada <script> que quiera ejecutar. El navegador evalúa: “si el script tiene el nonce que yo esperaba, lo corro; si no, chao”. El atacante, para ejecutar algo, necesita adivinar ese nonce. Y si lo generás correctamente —criptográficamente aleatorio, por request, impredecible— eso no va a pasar.

Cómo Implementar una Política CSP Estricta sin Romper Tu App

Para sitios renderizados en el servidor —como apps tradicionales o backend-heavy— esta es la mejor opción. Generás el nonce en el backend, lo pasás a la plantilla y lo aplicás a los scripts. Cada request, un nonce distinto. ¿El costo? Tenés que eliminar los scripts inline sin nonce, los handlers onclick, los javascript: en atributos href, y eliminar eval() del código.

La alternativa, ideal para sitios estáticos o SPAs que vivan en una CDN, es una CSP basada en hashes. Acá lo que hacés es calcular un SHA-256 del contenido exacto del script inline, lo codificás en Base64 y lo incluís en el header. Si el contenido cambia, el hash cambia. Así que ningún script modificado va a correr, ni aunque se parezca. Esto es perfecto para Angular, React, Vue y otros frameworks donde podés versionar y controlar cada línea del bundle.

¿Y qué pasa si el script de confianza carga otros scripts, como suele pasar con widgets de terceros? Ahí entra la joya: strict-dynamic. Esta directiva le dice al navegador: “Si este script fue permitido por hash o nonce, entonces también confío en los que él cargue dinámicamente”. Pero ojo, esto es una espada de doble filo: estás delegando confianza. Asegurate de usarlo solo con scripts que controles o que sean 100% confiables.

Entonces, un CSP estricto mínimo, basado en nonce, se ve más o menos así:

Content-Security-Policy: script-src ‘nonce-<RANDOM>’ ‘strict-dynamic’; object-src ‘none’; base-uri ‘none’

Y uno basado en hash podría ser:

Content-Security-Policy: script-src ‘sha256-abc123==’ ‘strict-dynamic’; object-src ‘none’; base-uri ‘none’

Ambos tienen lo que necesitás:

  • No permiten eval(), onclick=, ni javascript:.
  • Bloquean <object> y <base> que podrían ser manipulados.
  • Desconfían de todo lo que no esté explícitamente autorizado.
  • Son agnósticos del dominio de origen: no importa de dónde venga, si no tiene nonce o hash, no corre.

Implementar esto no es una tarea trivial. Vas a tener que refactorizar tu código, mover scripts a archivos separados o asegurarte de que el server pueda insertar el nonce en tiempo real. También vas a tener que eliminar handlers inline, cambiar eval() por JSON.parse() o Function, y asegurarte de que ningún widget legacy rompa todo.

Un error común: dejar unsafe-inline como fallback por miedo. Si hacés eso, arruinás todo el modelo. Evitalo. Y si necesitás compatibilidad con navegadores antiguos (cof cof, Safari pre-2020), podés agregar https: como backup. En navegadores modernos, eso se ignora. En los viejos, al menos restringe que todo venga de HTTPS.

Durante el despliegue, activá primero el modo report-only con el header Content-Security-Policy-Report-Only. Te va a mostrar qué se bloquearía sin romper el sitio. Podés monitorear los errores en la consola o enviar logs a un endpoint con report-uri o report-to. Una vez que estés seguro, lo pasás a Content-Security-Policy y lo hacés cumplir. Esa transición es crítica. No lo lances directo en producción sin ver qué rompe.

¿Limitaciones? Sí. CSP no es bala de plata. Si un atacante inyecta código dentro de un src dinámico, o logra manipular una plantilla en AngularJS antiguo, puede esquivar incluso un CSP estricto. También si tu política tiene unsafe-eval, o si alguna función como setTimeout() ejecuta código inyectado. Por eso, la sanitización sigue siendo obligatoria. CSP es defensa en profundidad, no un reemplazo para el sentido común ni para el encoding correcto.

Hay otra cosa que tenés que tener en cuenta: las bibliotecas modernas no siempre juegan bien con CSP. Algunos plugins, SDKs, o integraciones requieren inline scripts, eval, o cargas externas impredecibles. Si no podés modificarlos, vas a tener que buscar alternativas, abrir excepciones controladas, o meterlos en un sandbox (por ejemplo, un iframe con políticas CSP específicas).

Pero si lográs implementar un CSP estricto, basado en nonces o hashes, con strict-dynamic y sin inseguridades, habrás creado una de las barreras más sólidas que puede ofrecer el navegador. Tu sitio puede tener XSS, pero no ejecutarlo. Y eso, en el mundo real, marca la diferencia entre un sitio comprometido y uno que aguanta el embate.

Porque en seguridad ofensiva sabés cómo se explota un XSS. Pero si lo pensás del lado defensivo, un CSP bien pensado es el equivalente a sacarle la pólvora al atacante. Le queda la bala, le queda la pistola… pero no puede disparar. Y eso, en este juego, es una victoria.

Domina la Seguridad del Navegador

En el mundo real, los ataques web no vienen con una advertencia. El atacante no pregunta si tu framework está actualizado o si tu backend sigue OWASP. Simplemente explora, inyecta y ejecuta. Por eso, para frenar al atacante, tenés que controlar lo que el navegador ejecuta, y eso solo lo lográs con una combinación quirúrgica de encabezados de seguridad, reglas CSP, y aislamiento de contexto. No se trata de «agregar un header», sino de someter al navegador a una política de ejecución restrictiva, precisa y deliberada. Vamos a desmenuzar cómo hacerlo, como un hacker que se pasó al lado oscuro de la defensa.

Para crear estos objetos, puedes definir políticas de seguridad en las que te asegures de que las reglas de seguridad (como la escape o la limpieza) se apliquen de forma coherente antes de que los datos se escriban en el DOM. Estas políticas son los únicos lugares del código que podrían introducir XSS de DOM.

Ejemplos de uso

Cómo usar los tipos de confianza

 

Usos recomendados

  1. Aplica Trusted Types para los destinos de DOM peligrosos Encabezado de CSP y Trusted Types:
  1. Actualmente, ‘script’ es el único valor aceptable para la directiva require-trusted-types-for.
    Por supuesto, puedes combinar los tipos de confianza con otras directivas de CSP:

Combinación de un CSP basado en nonce de arriba con tipos de confianza:

<aside class=»note»><b>Nota: </b> Puedes limitar los nombres de políticas de tipos de confianza permitidos configurando una directiva <code>trusted-types</code> adicional (por ejemplo, <code>trusted-types myPolicy</code>). Sin embargo, esto no es un requisito. </aside>

Nota: Puedes definir políticas con nombres arbitrarios, a menos que limites los nombres de las políticas de tipos de confianza permitidas configurando la directiva trusted-types.

Con require-trusted-types-for ‘script’, usar un tipo de confianza es un requisito. Si usas cualquier API de DOM peligrosa con una cadena, se producirá un error.

XSS y la importancia de una CSP estricta con Trusted Types

Un error XSS en el backend o el frontend es una puerta abierta. Pero incluso si el atacante mete un payload, eso no significa que se va a ejecutar. CSP (Content Security Policy) permite que el navegador decida qué puede correr y qué no. Si tu política no permite scripts sin nonce o sin hash, el navegador los bloquea. Así de simple.

Pero no basta con bloquear <script> maliciosos. El XSS DOM-based ocurre cuando una función peligrosa como innerHTML, eval(), document.write(), o setTimeout() es alimentada con contenido dinámico no controlado. Ahí entra Trusted Types.

Con Trusted Types, le decís al navegador: “estas funciones peligrosas solo aceptan objetos TrustedHTML, TrustedScript o TrustedScriptURL”. No strings. Esto rompe el ciclo clásico de XSS DOM. Para usarlo, activás CSP con require-trusted-types-for ‘script’ y definís tus propias políticas de saneamiento. El navegador forzará que todos los sinks peligrosos sean alimentados por objetos creados vía esas políticas.

Ejemplo de CSP combinada con Trusted Types:

Content-Security-Policy: script-src ‘nonce-abc123’ ‘strict-dynamic’; require-trusted-types-for ‘script’; trusted-types default

Este header hace varias cosas:

  • Solo ejecuta scripts con nonce válido.
  • Obliga a que APIs como innerHTML acepten solo TrustedHTML.
  • Define una política llamada default que vos implementás para crear esos objetos confiables.

Así, no solo bloqueás scripts externos, también prevenís inyecciones silenciosas dentro de tu propio JavaScript.

X-Content-Type-Options: nosniff

Esto parece menor, pero no lo es. Cuando un navegador intenta “adivinar” el tipo de un archivo, podés terminar ejecutando un HTML en lugar de mostrar una imagen. Si esa imagen fue subida por un atacante y contiene HTML con JavaScript, adiviná qué pasa: XSS.

El header X-Content-Type-Options: nosniff le ordena al navegador que confíe solo en el Content-Type enviado por el servidor. Si subís una imagen con tipo image/png, pero el contenido es HTML, el navegador lo va a bloquear.

Siempre. Para todos los recursos.

X-Content-Type-Options: nosniff

Combinado con Content-Type correcto (text/html, application/json, etc.), previene ejecuciones inesperadas.

X-Frame-Options: protegiéndote de Clickjacking

Clickjacking es cuando un atacante embebe tu sitio en un iframe transparente sobre el suyo y hace que el usuario interactúe con botones invisibles. Esto sigue pasando en apps modernas con flujos de autenticación, formularios, o acciones críticas.

Con X-Frame-Options, podés evitar que tu sitio sea embebido:

X-Frame-Options: DENY

Esto lo bloquea en todos lados. O si necesitás permitirlo desde tu propio dominio:

X-Frame-Options: SAMEORIGIN

Pero si querés un control más fino —como permitir solo de un dominio específico— usá frame-ancestors de CSP. Por ejemplo:

Content-Security-Policy: frame-ancestors ‘self’ https://trusteddomain.com

CORP: Cross-Origin Resource Policy

Un atacante puede usar iframe, <img>, o <script> para cargar recursos de tu dominio en otro sitio y extraer datos o hacer fingerprinting. Para evitar esto, tenés que declarar desde tu servidor quién puede cargar qué.

El header Cross-Origin-Resource-Policy define esa restricción:

Cross-Origin-Resource-Policy: same-origin

Esto bloquea que otros sitios (diferente origen) carguen tu recurso. Si necesitás permitir solo dentro del mismo sitio (ej: subdominios), usá:

Cross-Origin-Resource-Policy: same-site

Y si querés permitir a cualquiera (como una CDN), usá:

Cross-Origin-Resource-Policy: cross-origin

Combiná CORP con COEP (más abajo) y tenés un entorno blindado de aislamiento.

COOP: Cross-Origin Opener Policy

COOP sirve para aislar tu documento de ventanas que se abren desde otros orígenes. Es una defensa poderosa contra ataques tipo Spectre o fugas de contexto entre ventanas.

Ejemplo:

Cross-Origin-Opener-Policy: same-origin

Esto asegura que, si alguien abre tu app en un popup, ese popup no tenga acceso al window.opener original si es de otro origen. Y si necesitás que un popup del mismo origen sí se comunique:

Cross-Origin-Opener-Policy: same-origin-allow-popups

COOP combinado con COEP (Cross-Origin Embedder Policy) es obligatorio si querés habilitar features avanzadas como SharedArrayBuffer o performance.measureUserAgentSpecificMemory().

CORS: no es solo un header

CORS no se configura en el frontend, sino en el servidor. Su objetivo es decir: “está bien, este otro sitio puede acceder a este recurso”. No es un mecanismo de seguridad, sino de control.

Ejemplo:

Access-Control-Allow-Origin: https://app.tusitio.com
Access-Control-Allow-Credentials: true

Esto le permite a ese origen hacer requests que incluyan cookies y headers autenticados. Pero si no controlás bien qué orígenes permitís, estás abriendo una vía de acceso externo. Por eso, nada de * si hay credentials: true.

Implementando todo junto

En un entorno de producción seguro, tu app debería estar entregando encabezados como estos:

Content-Security-Policy: script-src ‘nonce-abc123’ ‘strict-dynamic’; object-src ‘none’; base-uri ‘none’; require-trusted-types-for ‘script’; trusted-types default;
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Cross-Origin-Resource-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Access-Control-Allow-Origin: https://app.tusitio.com
Access-Control-Allow-Credentials: true

Cada uno cumple un rol específico. Todos juntos blindan la superficie de ataque del navegador desde múltiples ángulos. Esto no evita que haya bugs en tu código, pero reduce drásticamente el impacto de los errores explotables.

Recomendaciones finales

  • No confíes en que tu framework lo hace por vos. Ningún header se envía por magia. Auditá cada respuesta HTTP.
  • Empezá en modo report-only. Observá qué se rompe y qué se bloquea antes de aplicarlo.
  • Usá herramientas como Lighthouse, CSP Evaluator, o DevTools para inspeccionar problemas de implementación.
  • Educá al equipo. Un desarrollador que mete un onclick= o un eval() sin pensar puede sabotear toda tu política CSP.

Blindar tu app web no es cuestión de suerte, es cuestión de control. Controlar qué carga, qué ejecuta y con qué puede interactuar. Cuando tenés eso bajo dominio, dejás de rogar que no te ataquen y empezás a diseñar entornos donde los ataques simplemente no funcionan.

Eso es seguridad real. Eso es jugar a largo plazo.

XSS

Hay dos tipos de desarrolladores web: los que piensan que su aplicación está segura porque validan inputs, y los que ya sufrieron un ataque XSS y ahora codifican como paranoicos. Cross-Site Scripting es esa vulnerabilidad que nunca pasa de moda, que sigue mutando, que se esconde en los lugares menos esperados, y que sigue explotando a los que subestiman el impacto de un simple string malicioso. Si alguna vez pensaste que con filtrar <script> estabas del otro lado, te tengo noticias: no solo estás lejos, sino que probablemente ya estés comprometido.

El XSS moderno no es solo una cuestión de insertar código. Es una guerra de contexto, de interpretación del DOM, de manipulación del navegador y de escape de filtros mal implementados. Es meterse en los intestinos del renderizado web para encontrar un punto donde el navegador confíe en un dato y lo ejecute sin pensarlo. Porque eso es XSS: lograr que el navegador ejecute tu código en el contexto de otro. Y esa ejecución puede significar robo de cookies, secuestro de sesiones, explotación de credenciales OAuth, control de formularios, falsificación de interfaces, exfiltración de datos internos y hasta propagación automática a través de usuarios legítimos.

La anatomía de un XSS exitoso

Para que un ataque XSS funcione, no necesitás un RCE. No necesitás root. Solo necesitás que el navegador procese una variable controlada por vos y que esa variable termine en un sink ejecutable: innerHTML, eval(), document.write(), setTimeout() con strings, iframe[srcdoc], img[onerror], o cualquier punto donde el JavaScript dinámico se ejecute en el DOM.

El típico ataque empieza con la inyección: un parámetro mal validado, un campo mal escapado, un comentario que se muestra sin sanitizar. Si ese input entra en la respuesta HTML sin codificar, ya está: tu payload vive ahí. A partir de ahí, el juego es mantenerlo vivo sin ser detectado, hacerlo pasar como legítimo, y que se ejecute sin alertar a los WAFs, sin levantar alarmas en los SIEMs, sin que nadie note que el DOM fue manipulado.

Pero lo interesante empieza cuando entendés que no todo XSS es igual. Los reflejados (reflected) son los más conocidos, donde el payload va en la URL y vuelve en la respuesta. Los almacenados (stored) son peores: el payload se guarda en la base de datos y afecta a cada visitante. Pero hay un tercer tipo, más sutil, más elegante: el XSS basado en DOM. En este caso, el payload ni siquiera llega al servidor. El navegador lo genera a partir de un document.location.hash, document.URL, document.referrer u otra fuente controlada por el atacante. Es invisible para los filtros del backend, indetectable para los firewalls, y ejecutable directamente desde un bookmark o una URL falsa.

DOM XSS: el bypass definitivo a los filtros tradicionales

El DOM XSS representa un salto de paradigma. Ya no estás explotando al servidor, estás explotando cómo el frontend procesa el entorno. Si una página tiene un script que lee el document.location y lo usa directamente para renderizar contenido, estás en el negocio. Porque el servidor nunca verá tu payload. No hay logs. No hay entradas sospechosas en el backend. Todo se ejecuta del lado del cliente, sin intermediarios.

Ejemplo básico:

<script>
  var name = document.location.hash.substring(1);
  document.body.innerHTML = «Hola » + name;
</script>

Ahora accedés a la página con:

https://target.site/profile.html#<img src=x onerror=alert(1)>

Boom. Código ejecutado en contexto del sitio. Nada pasó por el servidor.

Y si pensás que los frameworks modernos te protegen, te aviso que todos tienen sus escape hatches. dangerouslySetInnerHTML en React. v-html en Vue. bypassSecurityTrustHtml en Angular. Todos pueden ser explotados si el developer los usa mal. Y muchos los usan mal, porque no entienden el riesgo. Porque creen que si el input es interno, está limpio. Spoiler: no lo está. Nunca lo está.

Técnicas de evasión: cuando los filtros fallan

Todo filtro puede romperse. Y los atacantes ya tienen el máster en evasión de WAFs. Usar codificación de entidades (%3Cscript%3E), comentarios intermedios (<scr<!– –>ipt>), fragmentos (#payload), control de atributos (<a foo=»bar target=_blank»>), o incluso abusing de MIME-sniffing con JSON y HTML embebido. Agregale a eso cargas maliciosas vía Flash (getURL(«javascript:…»)), SVG, o incluso documentos PDF, y tenés un arsenal para cada escenario.

Además, muchos sistemas implementan defensas rotas. Por ejemplo, usar filtros de entrada del lado del cliente que cualquier atacante puede saltar con un proxy. O confiar ciegamente en que su CSP los cubre, cuando en realidad están usando ‘unsafe-inline’ o ‘self’ con recursos comprometidos. O lo peor: intentar limpiar el input en lugar de escapar la salida. Porque eso es la clave: no confíes en el input. Nunca. Escapalo, codificalo, sanitizalo. Pero siempre al momento de renderizarlo, no antes.

Defensas reales: no te duermas en los laureles

Querés protegerte del XSS, bien. Acá va la receta real, sin bullshit:

Primero, usá frameworks con autoescaping. React, Angular, Vue, Svelte… todos ayudan, pero ninguno es infalible. Entendé qué partes escapan y cuáles no.

Segundo, escapá según contexto. ¿Es HTML? Usá codificación de entidades. ¿Es JavaScript? Escapá con \xHH. ¿CSS? Usá codificación hexadecimal CSS. ¿URLs? Encodeá con encodeURIComponent. Cada contexto tiene su escape, y usar el incorrecto es una puerta abierta.

Tercero, desinfectá si necesitás HTML editable. Usá librerías como DOMPurify para permitir HTML controlado. Pero mantenelas actualizadas. Son parte crítica de tu defensa.

Cuarto, implementá Trusted Types. Obligá al navegador a que solo reciba código seguro, con CSP y políticas estrictas. No más innerHTML = userInput. Solo innerHTML = TrustedHTML.

Quinto, monitoreá como si te jugaras la vida. No basta con bloquear. Tenés que loguear, alertar, auditar. Si hay un document.write() que empieza a escribir cosas raras, tenés que saberlo.

Sexto, no hagas offload de seguridad a un WAF. No sirve contra DOM XSS. No sirve si el payload va por el hash. No sirve si el ataque está ofuscado. Seguridad en el origen o no hay seguridad.

Y finalmente, educá a tus desarrolladores. Porque no hay parche que tape una cultura de desarrollo insegura. Si no entienden los contextos, si no saben cómo el navegador interpreta el HTML, CSS y JS, van a seguir abriendo puertas sin querer.

XSS como arma ofensiva

Desde el lado ofensivo, XSS sigue siendo una de las formas más eficientes de escalar privilegios. Un simple document.cookie puede darte acceso a un panel de admin. Un fetch() puede exfiltrar tokens. Un XMLHttpRequest puede simular acciones dentro del sistema. ¿Querés pivotear desde el frontend? Inyectás un iframe con tu C2, esperás el click, y ejecutás. ¿Querés persistencia? Usás cookies, localStorage o eventos onload con código autoejecutable.

Y si encima combinás XSS con CSRF, Clickjacking o vulnerabilidades de CORS mal configuradas, podés moverte lateralmente, escalar privilegios, e incluso tomar control completo de una organización. Todo desde el navegador. Sin shell. Sin ruido. Sin privilegios de sistema.

El peligro está en el navegador

El XSS no va a morir. Cambia de forma, se adapta, se esconde en nuevas APIs, pero sigue siendo una de las formas más efectivas de comprometer una aplicación web. Lo peor es que muchas veces ni siquiera necesitás una vulnerabilidad compleja. Solo necesitás que alguien se haya olvidado de escapar una variable. Y eso pasa. Todo el tiempo.

Si querés blindarte contra XSS, tenés que vivir con paranoia. Revisar cada punto de entrada. Validar cada campo. Escapar cada salida. Analizar cada script que manipula el DOM. Y aún así, sabé que un mal push puede romper todo. Así que auditá. Automatizá. Testeá. Escaneá. Y educá. Porque si vos no lo hacés, lo va a hacer alguien más. Y cuando lo haga, no va a ser para ayudarte.

Cómo Prevenir XSS como un Hacker

En el mundo real, XSS no es un “detalle técnico”, es un arma. Y si tu aplicación está expuesta, no importa cuántos WAF tengas delante o cuántas líneas de código de framework escribiste creyendo que estabas a salvo. Si no entendés cómo funciona XSS desde el navegador hacia abajo, estás en la mira.

Prevenir XSS no es poner un sanitize() y listo. No es confiar ciegamente en frameworks ni pensar que CSP va a tapar todo. XSS es uno de los ataques más ubicuos y adaptables que existen porque explota un principio básico de la web: el navegador le cree ciegamente al servidor.

En este artículo vas a ver qué hacer —y qué no— para blindar tus aplicaciones web contra XSS. Lo vas a ver desde la mirada de un atacante, pero con la disciplina quirúrgica de un defensor que quiere cerrar todas las puertas, incluso las que todavía no abriste.

Por qué XSS sigue funcionando en 2025

A pesar de librerías modernas, linters, typescript, CSP y training constante, el XSS no se fue. ¿Por qué?

Porque XSS no es un bug, es un desbalance de confianza: el servidor genera HTML dinámico, y el navegador lo interpreta sin cuestionar. Si en ese HTML aparece código JS, lo ejecuta. Si ese código fue inyectado por un atacante, ya no tenés control del DOM ni de la sesión del usuario. Y lo peor: muchas veces el código ni siquiera pasa por el backend, porque la inyección ocurre en el lado cliente (DOM-based XSS).

El enfoque defensivo real: pensar como atacante

Todo empieza en el modelo mental. No alcanza con validar entradas, ni con “no usar innerHTML”. La defensa efectiva contra XSS se basa en entender qué hace vulnerable una app: el flujo de datos desde una fuente no confiable hasta un sumidero peligroso. Ahí es donde entra el concepto de «contexto» y las técnicas reales de mitigación.

Tenés que mirar tu aplicación como una tubería. Si algo se mete en un extremo (input), puede salir disparado como un payload ejecutable en otro (output). La defensa XSS se basa en cerrar esa cadena en cada punto posible.

Fundamentos de una defensa XSS efectiva

1. Codificación de salida contextual

No todo se escapa igual. La clave no está solo en “sanear” o “validar”, sino en escapar para el contexto correcto:

  • Si vas a inyectar texto dentro del HTML (como contenido entre tags), codificá usando entidades HTML: &, <, > → &amp;, &lt;, &gt;
  • Si estás insertando dentro de atributos HTML (href, alt, etc.), escapá con entidades HEX: » → &#x22;
  • Si es dentro de JavaScript: escapá con \uXXXX o \xHH
  • En CSS: usá \000041 para codificar caracteres (sí, CSS también puede ejecutar ataques)
  • Para URLs: encodeá con encodeURIComponent() o %HH

La codificación debe ser acercada lo más posible al punto de renderizado, no en un interceptor general ni en la capa de entrada. El output es lo que importa.

2. Usar frameworks bien, sin escotillas de escape

Frameworks como React, Angular, Vue o Svelte hacen escape automático… hasta que no lo hacen. Porque todos tienen puertas traseras:

  • dangerouslySetInnerHTML en React
  • v-html en Vue
  • bypassSecurityTrustHtml en Angular
  • .innerHTML, htmlLiteral, unsafeHTML, etc.

Si vas a abrir esas puertas, sabé qué estás haciendo. De lo contrario, estás metiendo directamente payloads en sumideros peligrosos.

3. No metas datos en contextos inseguros

Hay lugares donde ni la mejor sanitización te salva. Estos son contextos inherentemente peligrosos:

  • eval(), setTimeout() o new Function() con entradas dinámicas
  • innerHTML, outerHTML, insertAdjacentHTML
  • srcdoc en <iframe>
  • onerror, onclick, onload embebidos en tags
  • URLs que usen javascript: o data: sin filtrar

Ni siquiera DOMPurify te protege si usás mal estas funciones. La regla es simple: no uses funciones interpretativas con datos que no controlás al 100%.

4. Sanitización de HTML solo cuando el usuario necesita escribir HTML

Hay casos reales en los que el usuario necesita poder estilizar o estructurar contenido (WYSIWYG, CMS, comentarios enriquecidos). En esos casos no podés escapar el HTML, pero sí podés sanearlo.

Usá librerías como DOMPurify, sanitize-html, HTMLSanitizer, etc. Pero no confíes ciegamente. Estas librerías necesitan mantenimiento y parches constantes. Y tené en cuenta:

  • Si modificás el HTML después de sanearlo, podés reintroducir XSS
  • Si se lo pasás a otra lib/framework, asegurate que no lo rompa
  • Nunca sanitices parcialmente. O lo sanitizás todo, o no sirve.

5. Aplicar una política CSP basada en nonce

CSP no previene XSS por sí sola. Pero una buena política reduce el daño posible y rompe muchos vectores.

El enfoque ideal: generar un nonce aleatorio por respuesta, y usar script-src ‘nonce-XYZ’. Eso desactiva inline scripts y hace que cualquier <script> inyectado falle.

Combiná eso con require-trusted-types-for ‘script’ y limitás aún más las APIs peligrosas.

6. Trusted Types: blindaje a nivel de tipo

Trusted Types es el nuevo paradigma para mitigar XSS DOM-based. Reemplaza las cadenas inseguras por objetos tipados seguros (TrustedHTML, TrustedScript, TrustedScriptURL). Y obliga a que todo pase por políticas explícitas.

  • Si un dev quiere usar .innerHTML, primero tiene que pasar por una política que cree un objeto TrustedHTML
  • Si no lo hace, el navegador lanza un TypeError

Esto cierra automáticamente muchos vectores DOM-based que se cuelan por validaciones superficiales. Y se integra con linters, bundlers y revisión de código. El impacto: menor superficie, menor riesgo, detección más fácil.

Contra qué no te va a proteger nada

Hay patrones de defensa que no funcionan (y todavía los ves en empresas grandes):

  • Validar solo en la entrada: si no codificás en la salida, estás regalando la ejecución
  • WAFs: no ven el DOM, no entienden contexto, se pueden evadir con encodings
  • Interceptors generales (como filtros de JavaEE): no saben en qué contexto se va a renderizar cada parámetro
  • Sanitización por regex: no sirve. HTML no se parsea con regex, punto.

Todos estos enfoques son parche. La solución real está en el diseño de tu flujo de datos y en aplicar escape/sanitización/contexto en cada punto crítico.

Una nota sobre fuentes confiables internas

Otro error común: confiar en la base de datos. Muchos desarrolladores creen que si los datos vienen de “un sistema interno” están limpios. Mentira. Si el origen permite que un usuario meta texto (como un nombre o dirección), y después otro sistema los muestra sin escape, tenés XSS persistente sin saberlo.

Un ejemplo: un cliente pone <script src=evil.js> en su dirección. Un agente de soporte entra a su perfil. Boom. Lo acabás de ejecutar en tu panel interno.

Conclusión: toda entrada es maliciosa hasta que se pruebe lo contrario.

Prevenir XSS no es limpiar texto, es controlar la ejecución

XSS no es un problema de strings. Es un problema de ejecución. Y la única forma de prevenirlo realmente es asegurarte de que el navegador no ejecute lo que no debería.

Eso significa:

  • Diseñar el flujo de datos con escape contextual
  • Usar sanitización solo cuando hace falta
  • Cortar funciones peligrosas con políticas y Trusted Types
  • Aplicar CSP de forma inteligente, no como un parche
  • Usar frameworks con cabeza, sin abrir puertas que no podés controlar
  • Asumir que toda fuente es peligrosa, incluso tu propia base de datos

Porque al final, si tu app ejecuta algo que no escribiste vos, alguien más lo hizo. Y eso nunca termina bien.

Cross-Site Request Forgery (CSRF): Cómo un Hacker Despierta al Gigante Dormido

El CSRF no es nuevo, pero sigue siendo uno de los ataques web más infravalorados y peligrosos que existen. A diferencia de otras vulnerabilidades como XSS o SQLi, que suelen requerir interacción directa con los inputs de una aplicación, CSRF se esconde en lo cotidiano. Está en las cookies, en los formularios, en las sesiones mal manejadas, y sobre todo, en el olvido. Porque cuando pensás que tu app está segura, que nadie puede hacer nada sin loguearse primero, ahí es donde CSRF entra en escena. Sin explotar bugs, sin romper lógica, sin ruido. Simplemente… usando tu propia aplicación contra vos.

¿Qué es un ataque CSRF y por qué sigue funcionando?

Cross-Site Request Forgery, o falsificación de solicitud entre sitios, es una técnica que permite a un atacante inducir a un usuario autenticado a realizar acciones no deseadas en una aplicación web. Pero no se trata de un phishing donde el usuario mete la contraseña en un sitio falso. Acá el usuario ya está autenticado. Ya tiene su cookie de sesión válida, su token de acceso activo, su MFA completo. El ataque se basa en abusar del navegador, que por diseño adjunta automáticamente las cookies en cada petición al dominio correspondiente.

Lo que hace el atacante es simple: construye un HTML malicioso con un formulario oculto, o una imagen con un src modificado, y se lo presenta a la víctima. Si la víctima está logueada en el sistema objetivo y visita la página del atacante, su navegador, sin preguntar nada, enviará una solicitud legítima al servidor vulnerable. El servidor verá que hay una cookie válida, aceptará la acción y la procesará. El usuario nunca verá nada. No hace falta engañarlo, ni robarle credenciales. Solo hace falta que tenga una sesión activa y que su navegador la use sin restricciones.

Y eso es exactamente lo que pasa cuando no tenés defensas anti-CSRF: tu propia lógica de autenticación se vuelve tu peor enemigo.

¿Qué tan grave puede ser?

Imaginá que tu aplicación permite cambiar el correo, resetear la contraseña, transferir fondos o incluso eliminar cuentas con una simple petición POST o GET. Ahora imaginá que un atacante arma un formulario oculto que hace justo eso, y lo embebe en un sitio de memes, en una campaña de phishing, o en un comentario malicioso en un blog.

<form action=»https://tu-sitio.com/usuario/cambiar_email» method=»POST»>
  <input type=»hidden» name=»email» value=»pwned@malicioso.com»>
</form>
<script>document.forms[0].submit();</script>

Si un usuario con sesión activa visita esa página, su navegador enviará la solicitud con su cookie de sesión. El servidor, sin ver nada raro, actualizará el correo. Y listo: el atacante ahora puede iniciar un flujo de recuperación de contraseña hacia ese nuevo correo y tomar control total de la cuenta.

El impacto escala de forma exponencial si el usuario comprometido es un administrador. Porque en ese caso, el atacante podría modificar permisos, crear nuevos usuarios privilegiados, o incluso borrar evidencia.

CSRF no necesita vulnerar nada. Solo necesita que te relajes.

A diferencia de XSS o inyecciones SQL, CSRF no explota una validación rota o una sanitización mal hecha. Explota la confianza del servidor en el navegador. Confianza ciega. Si el servidor no tiene un mecanismo extra que valide la intención de la acción (como un token anti-CSRF), entonces cualquier navegador con la cookie válida puede ejecutar cualquier acción. Así de simple.

Este tipo de ataques funcionan especialmente bien en:

  • Formularios sin token de verificación
  • APIs que aceptan solicitudes sin autenticación fuerte
  • Funciones expuestas con método GET que alteran estado
  • Aplicaciones que dependen solo de cookies y no de encabezados o tokens adicionales

Y lo peor es que ni siquiera vas a enterarte. A menos que monitorees acciones sospechosas, la modificación pasa como legítima. En tus logs vas a ver la IP del usuario, su sesión válida, y una acción aparentemente normal. Pero fue provocada por un tercero. Por eso es una de las vulnerabilidades más traicioneras.

¿Cómo evitar que CSRF te destruya?

La defensa real contra CSRF empieza por entender que autenticación no es lo mismo que autorización. El hecho de que una cookie sea válida no significa que la acción está permitida. Tiene que haber una prueba de intención. Un elemento que demuestre que el usuario realmente quiso hacer esa acción desde tu aplicación. Y para eso están los tokens anti-CSRF.

Un token anti-CSRF es un valor aleatorio, único por sesión o por solicitud, que el servidor genera y embebe en los formularios HTML. Cuando se envía el formulario, ese token va junto a la solicitud y el servidor lo valida. Si falta o es incorrecto, la solicitud se descarta. Así se rompe el ataque, porque el atacante no puede saber ni predecir el token de la víctima.

Pero hay un detalle clave: los tokens no pueden ir en la URL. Porque las URLs se filtran fácilmente, en logs, historial, o cabeceras de referer. Siempre que sea posible, deben ir en el cuerpo de la solicitud o en un encabezado personalizado.

Además del token, hay otras defensas complementarias que ayudan:

  • SameSite cookies: Si están configuradas como Strict o Lax, evitan que se envíen cookies en solicitudes cross-site. Pero muchos sitios todavía usan None para compatibilidad o errores de configuración.
  • Verificación del Referer: El servidor puede validar que la cabecera Referer provenga de su propio dominio. Pero esto es fácilmente saltado si el navegador no envía el encabezado (por ejemplo, por navegación HTTPS a HTTP, o configuraciones de privacidad).
  • CSRFGuard (OWASP): Es una solución completa para JavaEE que inyecta tokens en tiempo de ejecución usando JavaScript o etiquetas JSP. Además, protege enlaces y formularios automáticamente y permite configuraciones como un token por página, detección de ataques Ajax y validaciones de Referer.
  • Herramientas de generación de pruebas de concepto: Existen herramientas como Piñata-CSRF o la funcionalidad nativa en Burp Suite Professional que permiten crear exploits CSRF de forma automatizada. Estas herramientas facilitan la detección de endpoints vulnerables y la demostración del riesgo ante equipos técnicos o gerenciales.

¿Y si combino CSRF con XSS?

Ahí es donde empieza el infierno. Porque si lográs una inyección XSS en la aplicación, podés extraer el token anti-CSRF directamente del DOM o de la cookie. Eso te da la llave para saltar todas las defensas. Por eso siempre se recomienda eliminar las vulnerabilidades XSS antes de implementar defensas CSRF. Porque un XSS efectivo anula cualquier token que esté embebido en la página.

Además, con XSS podés automatizar la ejecución de CSRF desde el mismo sitio vulnerable, sin necesidad de atraer a la víctima a otro dominio. Todo pasa en el mismo contexto. Invisible. Letal.

Conceptos erróneos que siguen costando caro

Muchos desarrolladores creen que:

  • CORS evita CSRF (falso, CORS no protege contra formularios HTML)
  • Usar métodos distintos de POST evita CSRF (falso, los navegadores permiten POST desde cualquier origen)
  • Cambiar el Content-Type previene CSRF (falso, los navegadores pueden enviar application/x-www-form-urlencoded sin preflight)

Todo eso es basura. La única defensa real, consistente y probada es el uso de tokens anti-CSRF correctamente implementados, validados por el servidor, y regenerados por sesión o por solicitud.

El ataque invisible que espera su momento

El CSRF es la emboscada perfecta. No necesita ruido, ni acceso directo, ni herramientas complejas. Solo necesita que vos confíes ciegamente en el navegador. Si no tenés una defensa activa, si no validás tokens, si aceptás cualquier solicitud autenticada sin pruebas de intención, entonces estás expuesto. Así de simple.

Y cuando un atacante decida aprovecharlo, no va a ser para hacer una demo. Va a ser para vaciar cuentas, escalar privilegios, borrar auditorías o comprometer operaciones completas. Sin levantar una sola alerta. Porque todo lo que va a usar… ya está en tu código.

Dormite, y CSRF te va a despertar. Pero con una bomba.

A08:2025 – Fallos en la Integridad de Datos y Software

Esta categoría aparece cuando la aplicación confía ciegamente en datos, actualizaciones, integraciones o sistemas externos sin verificar su integridad o procedencia.

Incluye: manipulación de datos, integridad rota en pipelines, encabezados inseguros, repositorios externos, CI/CD comprometido, cargas de archivos mal validadas, deserialización insegura, actualizaciones sin firma y abuso de metadatos. Es crítica porque se rompe el fundamento más básico: “lo que recibo es lo que espero”.

🧩 Ejemplos típicos (2025)

  • Deserialización insegura en JSON, XML, Pickle, Java, PHP, .NET.
  • Archivos subidos sin verificación real de tipo o integridad.
  • Pipeline CI/CD que descarga scripts o paquetes sin verificar firma.
  • Actualizaciones de software sin firma criptográfica.
  • Manipulación de datos en tránsito sin integridad (faltan MAC/HMAC).
  • Firmas de documentos o tokens sin validación correcta.
  • Parámetros de integridad no validados en APIs (hash, signature).
  • Dependencias externas cargadas en runtime sin validación.
  • Falta de controles en Webhooks o colas de mensajes.
  • Aplicación que confía en encabezados controlados por el cliente (X-User, X-Role).
  • Falta de atomicidad en operaciones críticas (race conditions).
  • Configuración o firmware de IoT sin firma.

🔍 Mini-guía de explotación (pentesting 2025)

  1. Probar deserialización enviando payloads controlados.
  2. Manipular archivos subidos: cambiar extensión, MIME, contenido interno.
  3. Interceptar datos en tránsito → verificar si la integridad está protegida.
  4. Alterar parámetros sensibles:
    • amount, balance, price, role, status
  5. Probar el uso de encabezados falsificados (X-User: admin).
  6. Probar payloads en webhooks o colas con datos manipulados.
  7. Verificar si el software o contenedores se actualizan sin firma.
  8. Inyectar datos en pipelines CI/CD.
  9. Editar JSON/requests para forzar estados incoherentes.
  10. Probar race conditions para romper atomicidad.

🎯 Consecuencias

  • Manipulación total de datos (saldo, precios, inventario).
  • RCE por deserialización insegura.
  • Inyección de código o paquetes maliciosos.
  • Compromiso de pipelines CI/CD.
  • Actualizaciones maliciosas o backdoors instalados sin detección.
  • Elevación de privilegios mediante encabezados falsificados.
  • Corrupción de datos críticos o estados inválidos.
  • Violación de integridad en procesos internos y auditorías.

🛡 Defensas modernas (2025)

  • Verificación de integridad:
    • HMAC, firmas digitales, checksum SHA-256/512.
  • Validación estricta de archivos:
    • tamaño, magic numbers, MIME real, sandboxing.
  • Deserializar solo formatos seguros (JSON estricto; evitar objetos complejos).
  • Nunca deserializar objetos arbitrarios del cliente.
  • CI/CD seguro:
    • verificar firmas
    • evitar código externo en runtime
    • runners aislados
  • Validación estricta de webhooks y colas con firmas.
  • Validar encabezados sensibles del lado del servidor (no confiar en el cliente).
  • Atomicidad en operaciones críticas (transacciones).
  • Verificación de integridad en firmware/actualizaciones.
  • Políticas “deny-by-default” para cualquier dato sin procedencia clara.

📚 Subtipos / patrones modernos (2025)

Subtipo / patrónDefiniciónEjemplo bancarioPentesting (qué probar)Ataque / PoCConsecuenciaDefensaTips examen
Deserialización inseguraEjecutar objetos maliciososProcesar XML/PHP/JavaEnviar payloadRCEControl totalJSON seguro“Deserializar = peligro”
Upload sin validaciónArchivos manipuladosSubir PDF → .phpCambiar MIMERCE/File abuseToma servidorValidación MIMERevisar magic bytes
Encabezados confiadosApp confía en headers clienteX-Role: adminEnviar headers falsosEscaladaImpersonaciónIgnorar headersRevisar WAF/GW
Integridad rotaDatos sin HMAC o firmaModificar amountCambiar valoresFraudeManipulaciónHMACPreguntas sobre “tampering”
CI/CD inseguroPipeline sin firmasBuild descarga scriptAlterar URLBackdoorCompromiso totalFirmas/COSIGNBuscar “script remoto”
Firmware/updates sin firmaActualización manipulableATM/PoS update inseguroCambiar firmwareBackdoorFraude masivoSecure BootIoT sin firma = fallo
Race conditionsOperaciones no atómicasDoble débitoEnviar reqs simultáneasDoble gastoPérdida $LocksRace = integridad
JSON tamperingParámetros críticos alteradosbalance, statusCambiar JSONAlterar datosFraudeValidar campos“Precio oculto”
Webhooks insegurosSin firma/verificaciónPayment success webhookFalsificar POSTConfirmar pago falsoFraudeValidar firmaBuscar X-Signature
Repos externos insegurosConfianza ciegaDescarga lib externaCambiar URLInyecciónCompromisoFirmasExternal = riesgo

🧪 Checklist rápida de pentesting A08:2025

  • Buscar deserialización en endpoints (XML, PHP, Java, Pickle, .NET).
  • Probar uploads con múltiples extensiones.
  • Validar magic bytes y MIME real.
  • Enviar headers falsificados.
  • Manipular parámetros sensibles (price, status, role).
  • Probar alteración de webhooks.
  • Revisar si pipelines descargan código externo.
  • Verificar que updates/firmware tengan firma válida.
  • Simular race conditions.
  • Alterar JSON para romper integridad.
  • Explorar colas de mensajes (SQS, Kafka, Redis).
  • Verificar HMAC o signatures en cada request sensible.

🧾 Resumen ejecutivo para tu curso

A08:2025 Fallos en la Integridad de Datos y Software ocurre cuando una aplicación confía en datos, archivos, actualizaciones, encabezados, integraciones o pipelines sin verificar su integridad o autenticidad. Esto permite manipulación de datos, ejecución remota de código, alteración de estados, fraude, compromisos completos en CI/CD, carga de archivos maliciosos y actualizaciones adulteradas. La mitigación depende de verificación de firmas, HMAC, validación estricta de archivos, transacciones atómicas, deserialización segura, pipelines robustos y estricta validación del origen de cada dato. Si se rompe la integridad, se rompe el corazón del sistema.

Tips de examen

  • Palabras clave: “update sin firma”, “supply chain”, “deserialización insegura”, “pipeline comprometido”, “datos sin hash/firma” → todo apunta a A08.
  • Defensa correcta: firmas digitales, repos confiables, hashes, SBOM, CI/CD seguro, validación de integridad.
  • Diferencia con otros:
    • A06 (Vulnerable Components): el software es viejo/vulnerable.
    • A08 (Integrity Failures): el software o datos fueron manipulados o aceptados sin validación.

Preguntas

¿Qué ocurre si una aplicación bancaria descarga actualizaciones sin verificar firma digital? Software and Data Integrity Failures, porque no valida integridad de las actualizaciones.

¿Qué pasa si el banco usa una librería de un repositorio público sin verificar hashes o procedencia? Software and Data Integrity Failures por dependencia no confiable.

¿Qué vulnerabilidad se presenta cuando un pipeline de CI/CD permite modificar scripts de build sin validación? Software and Data Integrity Failures por pipeline inseguro (riesgo de inyección en supply chain).

¿Qué ocurre si una aplicación no firma ni valida los archivos de transacciones generados para auditoría? Software and Data Integrity Failures, porque los datos pueden alterarse sin detección.

¿Qué falla existe si la app serializa objetos de usuario en Java y los procesa sin validación, permitiendo que un atacante modifique los datos serializados? Software and Data Integrity Failures por Insecure Deserialization.

¿Qué ocurre si un atacante logra introducir código malicioso en una librería de terceros que luego se despliega en el banco? Software and Data Integrity Failures por ataque de supply chain.

¿Qué pasa si un banco no verifica la integridad de logs críticos y un administrador los borra o altera? Software and Data Integrity Failures por falta de validación de integridad de datos.

¿Qué ocurre si el banco depende de runtimes EOL (ej. Java 6) y no verifica integridad de builds? Software and Data Integrity Failures por uso de entornos inseguros sin validación.

¿Qué vulnerabilidad hay si el banco distribuye APKs móviles sin firma digital válida? Software and Data Integrity Failures porque el usuario no puede verificar autenticidad de la app.

¿Qué ocurre si un atacante logra modificar archivos de configuración en un repositorio Git del banco y esos cambios se aplican automáticamente en producción?  Software and Data Integrity Failures por pipeline de despliegue sin controles de integridad.

¿Cuál es la diferencia entre A06 (Vulnerable Components) y A08 (Integrity Failures)? A06 = usar versiones viejas/vulnerables; A08 = confiar en software/datos sin validar su integridad o procedencia.

¿Qué controles debe aplicar un banco para prevenir Software & Data Integrity Failures? Firmas digitales en actualizaciones, validación de dependencias con hashes, SBOM, seguridad en CI/CD, uso de repositorios confiables, serialización segura, firmas/HMAC en datos críticos.

¿Qué ocurre si un banco no usa HMAC o firmas en mensajes entre sistemas internos? Software and Data Integrity Failures porque los datos pueden ser modificados en tránsito sin detección.

¿Qué vulnerabilidad se presenta si no se valida la integridad de scripts externos cargados desde un CDN (ej. JS sin SRI)?  Software and Data Integrity Failures por falta de verificación de integridad en recursos externos.

¿Qué impacto tendría un ataque de supply chain en un banco? Compromiso total de la infraestructura y posibilidad de fraude masivo mediante software legítimo manipulado.

  1. Update sin firma digital → A08.
  2. Uso de librería de repo público sin validación → A08.
  3. CI/CD pipeline inseguro → A08.
  4. Falta de firma en transacciones/logs → A08.
  5. Insecure Deserialization → A08.
  6. Diferencia A06 vs A08 (versión vieja vs integridad no validada).
  7. Defensa correcta: firmas, hashes, SBOM, CI/CD seguro, repositorios confiables.

¡Excelente trabajo!

En este capítulo aprendiste no solo qué es OWASP Top 10, sino cómo ha evolucionado, qué representa cada categoría y por qué son tan importantes para el desarrollo seguro. Te adentraste especialmente en A08:2025, una de las vulnerabilidades más críticas del panorama actual, entendiendo cómo se explota y cómo prevenirla con controles modernos de integridad, seguridad del navegador y aislamiento de recursos. Ahora comprendes que la seguridad no consiste solo en “evitar errores”, sino en controlar la confianza, validar la procedencia y proteger cada capa del sistema.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *