En junio de 2024, investigadores de Sansec y Cloudflare detectaron que polyfill.io, un CDN ampliamente usado para servir código de compatibilidad JavaScript, había sido comprado meses antes por una empresa china. El nuevo propietario empezó a modificar el script que entregaba para inyectar código malicioso — redirecciones a sitios de apuestas, spam, potencialmente keyloggers — en más de 100.000 sitios web que lo referenciaban directamente.
El ataque no requirió hackear ningún servidor. No hubo contraseña robada ni CVE. El atacante simplemente compró el dominio, esperó, y aprovechó que decenas de miles de sitios confiaban ciegamente en lo que ese CDN decidiera enviarles.
Un atributo HTML que nadie había agregado — integrity — habría bloqueado el ataque en todos esos sitios antes de que el primer byte malicioso se ejecutara.
Qué es Subresource Integrity
Cuando cargas un script o stylesheet desde un CDN externo, el browser descarga el archivo y lo ejecuta. No sabe si ese archivo es el mismo que el desarrollador originalmente referenció, ni si fue modificado en tránsito o en origen. Confía.
Subresource Integrity (SRI) cambia eso. Es un mecanismo del browser que te permite declarar, en tu propio HTML, un hash criptográfico del recurso que estás cargando. Antes de ejecutar cualquier código, el browser calcula el hash del archivo recibido y lo compara con el que declaraste. Si no coinciden, bloquea la ejecución y lanza un error de integridad. No hay forma de eludir esto desde el servidor externo — el control queda en tu HTML.
Cómo se ve en código
Sin SRI:
<script src="https://cdn.proveedor.com/jquery-3.7.1.min.js"></script>
Con SRI:
<script
src="https://cdn.proveedor.com/jquery-3.7.1.min.js"
integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs"
crossorigin="anonymous">
</script>
Dos atributos:
integrity: el algoritmo (sha256,sha384osha512) seguido de un guión y el hash en Base64 del contenido esperado. SHA-384 o SHA-512 son los recomendados hoy.crossorigin="anonymous": necesario para que el browser aplique CORS y tenga acceso al contenido del recurso para verificarlo. Sin este atributo, SRI no funciona aunque declares el hash.
Lo mismo aplica para <link rel="stylesheet">:
<link
rel="stylesheet"
href="https://cdn.proveedor.com/bootstrap-5.3.3.min.css"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
Cómo se genera el hash
No tienes que calcularlo a mano. Tres opciones:
Opción 1 — línea de comando (en cualquier sistema con openssl o shasum):
curl -s https://cdn.proveedor.com/jquery-3.7.1.min.js \
| openssl dgst -sha384 -binary \
| openssl base64 -A
El resultado lo pegas después de sha384-.
Opción 2 — generador web oficial de SRI Hash Generator (srihash.org): pegás la URL, elige el algoritmo, y te entrega el atributo integrity listo para copiar.
Opción 3 — el CDN mismo: cdnjs.com, jsDelivr y Bootstrap CDN ya muestran el hash SRI en su interfaz junto al código de embed. Si copiás desde ahí, ya viene incluido.
Qué bloquea y qué no
SRI protege contra:
- CDN comprometido (el caso polyfill.io): si el proveedor modifica el archivo, el hash deja de coincidir y el browser bloquea.
- Ataque man-in-the-middle en la conexión al CDN: si alguien intercepta la respuesta y modifica el contenido en tránsito, el hash falla.
- Errores silenciosos de versión: si el CDN sirve una versión diferente a la que esperabas (accidental o malicioso), lo detectas de inmediato.
SRI no protege contra:
- Scripts cargados dinámicamente en runtime (con
document.createElement('script')): SRI solo cubre atributos declarados estáticamente en el HTML. - Recursos de tu propio dominio: SRI es para terceros. Tus propios archivos los controlás vos.
- XSS: si hay inyección XSS en tu página, el atacante puede inyectar un
<script>nuevo sinintegrity. SRI no es sustituto de CSP ni de sanitización de input. - El recurso de primer partido (tu propio HTML): SRI cubre los recursos que tu página carga, no el HTML en sí.
El atributo crossorigin importa más de lo que parece
Un error frecuente: agregar integrity pero olvidar crossorigin="anonymous". El resultado es que el browser no puede verificar el hash (por restricciones CORS) y, dependiendo del browser y de si la URL es HTTPS o no, puede comportarse de formas inconsistentes — desde ignorar el atributo hasta bloquear el recurso igual.
La regla es simple: si usás integrity, siempre agregá crossorigin="anonymous". Son inseparables.
Implementación en WordPress
WordPress es el CMS más frecuente entre los sitios que detectamos con JS-SRI-MISSING. Los scripts de jQuery, el loader de Google Fonts, Slick Slider y docenas de plugins se cargan desde CDNs externos sin SRI.
La forma más prolija de implementarlo en WordPress es enganchar el filtro script_loader_tag:
// En functions.php de tu tema hijo
add_filter('script_loader_tag', function($tag, $handle, $src) {
$sri_map = [
'jquery' => 'sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs',
'slick-js' => 'sha384-xxxxxxxxxxxxxxxx',
];
if (isset($sri_map[$handle])) {
$hash = $sri_map[$handle];
$tag = str_replace(
"src='$src'",
"src='$src' integrity='sha384-{$hash}' crossorigin='anonymous'",
$tag
);
}
return $tag;
}, 10, 3);
Atención con versiones: SRI vincula el hash a una versión exacta del archivo. Si tu plugin actualiza jQuery de 3.7.1 a 3.7.2, el hash deja de coincidir y el script se bloquea. Tenés que mantener el mapa actualizado cada vez que cambia una versión. Por eso la implementación práctica es:
- Identifica los scripts de CDNs externos que no controlás.
- Fija las versiones en tu
functions.php(deshabilitá actualizaciones automáticas para esas dependencias o hacelas manualmente). - Generá el hash por versión y guardalo en el mapa.
- Cada vez que actualizás una librería, regenerás el hash.
Para plugins de terceros que cargan sus propias dependencias de CDN, revisá si el plugin tiene soporte SRI nativo (algunos ya lo implementan) o si podés desencolar su script y reencolar el tuyo con SRI.
Qué detecta FreeScan
El scanner JS-SRI-MISSING de FreeScan analiza el HTML de tu sitio e identifica tags <script src="..."> y <link rel="stylesheet" href="..."> que:
- Cargan desde un dominio diferente al tuyo (recursos de terceros).
- No tienen atributo
integritydeclarado.
Para cada recurso encontrado, el informe indica el dominio de origen y el tipo de tag. El hallazgo se clasifica como Media porque la explotabilidad depende de que el CDN sea comprometido — no es algo que un atacante externo pueda disparar directamente contra vos, pero el impacto cuando ocurre es total.
El fix es directamente aplicable: generás el hash del recurso actual y agregás el atributo. El trabajo es de minutos por recurso.
Un patrón concreto que vemos seguido
El caso más frecuente en sitios chilenos que pasaron por FreeScan: jQuery cargado desde code.jquery.com o cdnjs.cloudflare.com sin SRI. jQuery en particular es un blanco de alta rentabilidad para atacantes — presente en millones de sitios, acceso al DOM completo, historial de vulnerabilidades. Si tu CDN de jQuery queda comprometido (o simplemente responde de forma distinta en un A/B test que salió mal), SRI es lo único que te protege antes de que el código llegue al browser de tus usuarios.
El segundo caso más frecuente: Google Fonts. Técnicamente es menos riesgoso (son solo estilos, no ejecutan código), pero el principio es el mismo.
La línea de defensa de SRI es corta — dos atributos, un hash que calculás una vez — y actúa en el único momento en que todavía podés parar el ataque: antes de que el código se ejecute. El incidente de polyfill.io de 2024 mostró que los CDNs de terceros pueden cambiar de propietario, de política o de contenido sin aviso. Lo que no podés controlar en el origen, al menos podés verificarlo en destino.