Eludir SRI con una "carrera" entre CDNs: cómo la competencia en la carga compromete la protección de scripts

Eludir SRI con una "carrera" entre CDNs: cómo la competencia en la carga compromete la protección de scripts

La Integridad de subrecursos (SRI) es un mecanismo que los desarrolladores usan para protegerse contra la sustitución de recursos externos, por ejemplo bibliotecas JavaScript cargadas desde una red de distribución de contenido (CDN). En teoría, si un atacante intenta modificar el archivo, el navegador detectará la discrepancia del hash y simplemente no ejecutará el código.

En la práctica no es tan sencillo. Existe un escenario conocido como CDN race, en el que un atacante puede eludir la verificación SRI aprovechando las particularidades del funcionamiento de la red CDN y la carga paralela de recursos por parte del navegador. Esta técnica no es nueva, pero sigue siendo relevante, especialmente en proyectos grandes con alta carga y complejas cadenas de caché.

Qué es SRI y para qué sirve

SRI ( Integridad de subrecursos) — es el atributo integrity que se añade a la etiqueta <script> o <link>. Almacena el hash del contenido del archivo (por lo general SHA-256, SHA-384 o SHA-512). Cuando el navegador descarga el recurso, calcula su hash y lo compara con el especificado en el atributo. Si no coinciden, el archivo se descarta.

Ejemplo de uso:

<script src="https://cdn.example.com/lib.js"
        integrity="sha384-abc123..."
        crossorigin="anonymous"></script>

Esto es útil si no confía completamente en el hosting externo. Incluso si la red CDN se ve comprometida, el navegador protegerá al usuario negándose a ejecutar código modificado.

Cómo funciona CDN race

CDN race es una situación en la que diferentes servidores de la CDN pueden servir simultáneamente distintas versiones del mismo archivo según el punto de presencia (PoP) y el estado de la caché. Debido a la naturaleza distribuida de la red CDN y a las particularidades de la actualización de contenido, pueden aparecer "ventanas" de tiempo en las que una parte de los nodos ya contiene la versión actualizada (o maliciosa) y otra parte no.

El punto clave es que el navegador puede cargar en paralelo el mismo recurso en diferentes contextos, por ejemplo:

  • El mismo script está incluido varias veces con diferentes parámetros.
  • El script se carga como módulo (type="module") y como script normal.
  • Existe un preload (<link rel="preload">) y la carga real.

Si una de las solicitudes obtiene la versión "limpia" y pasa la verificación SRI, y la otra la maliciosa, puede darse una carrera por el uso de la caché y la ejecución del código antes de que se complete la verificación.

Por qué es posible

En un mundo ideal, SRI siempre verifica los bytes concretos que se van a ejecutar. Pero en los navegadores reales hay una matización: si el recurso ya está en la caché y pasó la verificación en un contexto, esa misma caché puede reutilizarse en otro —incluso si la solicitud provino de otro punto de la CDN y habría devuelto otra respuesta.

Esto abre el espacio para ataques:

  1. El atacante modifica el archivo en algunas PoP de la CDN, pero no en todas.
  2. La víctima envía solicitudes paralelas al recurso.
  3. El navegador almacena en caché la versión "limpia" después de una verificación.
  4. Otro contexto o mecanismo de carga ejecuta el código manipulado, evitando una verificación adicional.

Ejemplo de escenario de ataque

Consideremos un modelo simplificado:

  1. En el sitio se incluye main.js con SRI y crossorigin.
  2. La CDN tiene PoP en Londres y en Nueva York.
  3. El atacante consigue acceso a la CDN en Nueva York y carga una versión maliciosa.
  4. Un usuario en Europa carga la página; el navegador, mediante balanceo DNS, envía una solicitud a Londres y otra a Nueva York.
  5. El PoP de Londres sirve la versión legítima, que pasa la verificación SRI y se almacena en caché.
  6. El PoP de Nueva York sirve el archivo malicioso, pero el navegador utiliza la versión ya almacenada en caché o ejecuta el código antes de la verificación.

Como resultado, la protección que parece existir no salva frente a una amenaza real.

Por qué es peligroso para proyectos grandes

Esta técnica es especialmente relevante para proyectos que:

  • Usan CDN sin un control estricto de la caché.
  • Cargan el mismo recurso desde varios lugares (por ejemplo, con parámetros ?v=).
  • Usan activamente preload o prefetch.
  • Tienen cadenas complejas de redirecciones y balanceadores.

Cuanto más compleja sea la infraestructura, mayor será la probabilidad de que en algún lugar se puedan crear condiciones para la carrera de carga.

Cómo protegerse contra CDN race

Eliminar completamente este riesgo es difícil, pero se puede minimizar:

  • Usar la API Fetch (fetch) o cargar recursos con validación manual.
  • Desactivar o minimizar las cargas paralelas de un mismo recurso en diferentes contextos.
  • Controlar estrictamente la caché de la CDN, incluyendo la sincronización forzada de los PoP antes del despliegue.
  • Usar versionado de archivos y cargar nombres únicos en cada actualización.
  • Cuando sea posible, alojar los scripts críticos en servidores propios.

Herramientas para pruebas

Si desea comprobar su proyecto frente a este tipo de ataques, puede usar:

  • SecurityHeaders — para verificar SRI y otros encabezados.
  • CDN de prueba propios que emulen latencias y distintas versiones de archivos.
  • Herramientas como mitmproxy para sustituir respuestas al vuelo.

Conclusiones

SRI es un mecanismo útil, pero no es una panacea. Protege contra la sustitución de contenido en la mayoría de los casos, pero en infraestructuras reales con CDN siguen existiendo vulnerabilidades. CDN race es un ejemplo de cómo las particularidades arquitectónicas pueden convertir una protección potente en un escudo con agujeros.

Si su proyecto utiliza recursos externos, especialmente a través de CDN globales, conviene tener en cuenta estos escenarios. Y, mejor aún, diseñar el sistema de modo que los archivos críticos estén bajo su control total.

Alt text