Configurar correctamente la integridad de los subrecursos (SRI) con el CDN de Cloudflare

El día de ayer publiqué la nueva versión de mi sitio web, pero antes de publicarlo tuve problemas con el CDN –Content Delivery Network, o Red de distribución de contenidos en español– de Cloudflare: los subrecursos CSS y javascript de la página estaban disponibles, pero por alguna razón el navegador no los cargaba.

Subresource Integrity (SRI), o la integridad de los subrecursos

El SRI es una característica de seguridad que garantiza que los subrecursos de una página web –archivos CSS y JS principalmente– no hayan sido manipulados.

Esta característica es importante, ya que hoy en día la mayoría de sitios web optan por usar una red de distribución de contenidos, y se estima un crecimiento en el uso de esta tecnologías en los próximos años. Para entender la importancia del SRI hay que entender primero lo que es un CDN: es una red global que permite cargar las páginas web más rápido. Servicios como Netflix, Youtube, Deezer y esencialmente todos los servicios de steaming lo usan. La estrategia principal de un CDN es copiar un vídeo subido en algún país de latinoamérica, y pegarlo en varios puntos estratégicos de las diferentes regiones del mundo. Así, si alguien en España quiere ver el vídeo, podrá hacerlo rapidamente, ya que el CDN se ha encargado de hacer la transferencia de manera previa desde latinoamérica a Europa.

Este copia y pega conlleva una situación de seguridad: el contenido, ya sea una imagen, vídeo o incluso los archivos CSS y JS de una página web, son distribuidos a través de una red que el dueño del sitio no controla y tampoco tiene acceso. Al usar un CDN estás cediendo la distribución a una red externa que no puedes controlar. Tú entregas el contenido original y esperas que este contenido sea el mismo para todos los usuarios, pero hay actores malintencionados dentro de estas redes que buscan la manera de manipular dichos contenidos para sus propios beneficios.

En octubre de 2019 un grupo de ciberseguridad alemán logró efectuar un ataque que afectó a redes CDN. Los sitios afectados mostraban páginas de error en vez del contenido legítimo a los usuarios finales. Recalco que el grupo de ciberseguridad en ningún momento obtuvo el control del servidor donde se alojaba el sitio web ni la red CDN. Esto quiere decir que cualquier persona con conocimiento suficiente puede manipular los contenidos distribuidos por un CDN sin siquiera tener un usuario ni una contraseña. Esto es preocupante si no se toman las medidas de seguridad correspondientes.

Aquí entra el SRI: es una pequeña cadena de texto que se usa para verificar que el contenido que entregó el CDN al usuario final sea el que se distribuyó originalmente.

<!-- Subrecurso sin SRI -->
<script src="mi-app.js"></script>

<!-- Subrecurso con SRI -->
<script src="mi-app.js" integrity="sha256-rqwup5jPgTd…"></script>

La cadena de texto SRI es el resultado de una función hash criptográfica aplicada al contenido del subrecurso. Esta cadena de texto se almacena y distribuye originalmente a través de un atributo integrity en los elementos HTML. Al final de la cadena de distribución, el navegador web vuelve a calcular el hash del contenido entregado por el CDN, y la cadena de texto resultante debe coincidir con la original para garantizar que es el mismo contenido. Si la cadena difiere, el contenido fue manipulado y el navegador, por seguridad, no lo cargará.

Mensaje del navegador en donde informa del bloqueo de un subrecurso debido a que su hash es inválido.

Optimización de recursos estáticos en Cloudflare

Cloudflare ofrece la opción de minimizar el tamaño de los subrecursos para acelerar la carga de las páginas web: entre más pequeño sea un archivo, menos tiempo tomará su descarga. La minificación reduce el tamaño de los subrecursos sin alterar su funcionalidad, esto quiere decir que podemos obtener la misma función que hemos programado en menos espacio. Esto es útil cuando las dependencias pesan mucho o cuando prescindimos de la optimización de nuestros propios scripts. Funciona muy bien, sin embargo hay un problema cuando hemos implementado SRI: el hash de nuestro contenido original y no minificado va a variar con respecto al contenido optimizado y minificado por Cloudflare. Aunque la función es la misma, el hash es diferente, ya que la función criptográfica no lo calcula en relación a la funcionalidad del sript, sino al contenido. Al minificar el recurso, su contenido cambia, y por lo tanto el hash también. El resultado final es el bloqueo del subrecurso por el navegador.

Por ejemplo: tenemos un recurso no optimizado en javascript:

var array = [];
for (var i = 0; i < 20; i++) {
  array[i] = i;
}

Este recurso pesa 65 bytes. Como la función hash se aplica a nivel de bytes, tenemos que su contenido representado en bytes es el siguiente:

0000000 76 61 72 20 61 72 72 61 79 20 3d 20 5b 5d 3b 0a
0000010 66 6f 72 20 28 76 61 72 20 69 20 3d 20 30 3b 20
0000020 69 20 3c 20 32 30 3b 20 69 2b 2b 29 20 7b 0a 20
0000030 20 61 72 72 61 79 5b 69 5d 20 3d 20 69 3b 0a 7d
0000040 0a
0000041

Y para esta cadena, el valor hash de una función tipo HMAC GOST es 2647098b.

Al minificar el mismo recurso podríamos obtener algo similar a esto:

for(var a=[i=0];i<20;a[i]=i++);

En la práctica realiza la misma función, pero su peso es de 32 bytes. La optimización ha reducido su peso a la mitad, y también ha cambiado el algoritmo a una más eficiente.

Su contenido representado en bytes ha cambiado:

0000000 66 6f 72 28 76 61 72 20 61 3d 5b 69 3d 30 5d 3b
0000010 69 3c 32 30 3b 61 5b 69 5d 3d 69 2b 2b 29 3b 0a
0000020

Y por lo tanto, el valor de la función hash HMAC GOST ahora es 81193395.

El valor original 2647098b es diferente a 81193395. Ha habido una alteración y por lo tanto el navegador bloqueará el contenido por seguridad.

Solucionar el problema de SRI con la optimización de Cloudflare

Lamentablemente, ambas características son incompatibles entre sí. Depender de características de optimización de lado del CDN para manejar la seguridad del sitio también va en contra del principio básico del SRI. Yo solucioné este problema al desactivar la minificación de recursos CSS y JS de Cloudflare.

Lo primero que hice fue identificar el problema. Las herramientas de desarrollador del navegador me dieron la primera pista. Ahora, mi contenido cargaba correctamente en mi entorno local y apenas subía los cambios al servidor también cargaban correctamente, pero luego de unos minutos el contenido dejaba de cargar y volvía a aparecer el error. Esto se debe a que la minificación de Cloudflare tarda unos minutos antes de aplicarse completamente. Durante ese transcurso de tiempo, el contenido cargaba. Una vez el contenido era alterado por la minificación, el contenido dejaba de cargar.

La solución fácil es desactivar el RSI de los recursos, pero así perdería completamente el control de los subrecursos y sería vulnerable a diferentes tipos de ataques. Casi nunca es recomendable desactivar una característica de seguridad, y menos en un ambiente tan público como lo son los CDN e internet.

También había muchos lugares en donde se recomendaba añadir el atributo crossorigin="anonymous" al subrecurso. Esto no funciona para solucionar problemas relacionados con SRI, pero la gente los confunde ya que los síntomas son similares. Este atributo modifica el comportamiento de otra característica de seguridad llamada CORS, y no tiene ningún impacto en SRI.

CORS tampoco se aplicaba a mi problema ya que el dominio del CDN es igual al dominio que invoca la carga. Esto debido a que el CDN de Cloudflare funciona con el mismo nombre de dominio. Vale la pena aclarar que, si se usa otro CDN como Cloudfront, el nombre de dominio cambia y por lo tanto se este atributo sí funcionaría.

Así que la solución era clara: desactivar la minificación. No toqué la configuración de Cloudflare en mucho tiempo, por lo que no recordaba si había una opción similar, pero por experiencia sabía que los CDN tenían opciones y estrategias además de la distribución geográfica para optimizar los recursos. Encontré la opción Minificador automático bajo la opción Speed. Esta opción permite minificar los recursos javascript, CSS y HTML. Como los subrecursos míos eran solo javascript y CSS, solo deshabilité esas dos opciones. De esa forma Cloudflare no alteraría mis recursos, y de esta forma los valores hash de mis recursos no cambiarían y podrían ser cargados de nuevo por el navegador.

Opción Minificador automático de Cloudflare con las opciones JavaScript y CSS deshabilitadas.

Luego de hacer este cambio purgué toda la caché de Cloudflare, esperé un pr de minutos, y el sitio web empezó a cargar correctamente los subrecursos.

Ahora, ya que la optimización está deshabilitada en el CDN, es necesario ocuparnos de la optimización desde nuestro lado. Gestores de paquetes y dependencias como Webpack hacen esto automaticamente, por lo que también es recomendable usar herramientas de este tipo para administrar los códigos CSS y javascript –y demás recursos–.

Si no se cuenta con Webpack, también se pueden usar herramientas como uglifyjs y optimizar manualmente los recursos.

Configuración en Webpack

Webpack por defecto habilita el algoritmo hash SHA-384, pero siempre es bueno tener un algoritmo retrocompatible disponible, como lo es SHA-256.

En nuestro archivo de configuración de Webpack, buscamos la llamada a la función enablentegrityHashes() y pasamos en el segundo argumento un arreglo con los dos algoritmos hash que deseamos que Webpack calcule para nuestros subrecursos:

.enableIntegrityHashes(true, ['sha256', 'sha384'])

Ahora los valores de integridad tendrán ambos valores hash:

{
  "integrity": {
    "/mi-app.js": "sha256-tPpWfL8S… sha384-/Zh0hTCg…"
  }
}

Conclusión

SRI protege de ataques como XSS o envenenamiento de la caché. Es muy recomendado usarlo cuando se opta por distribuir los subrecursos a través de un CDN.

SRI es incompatible por definición por posprocesamientos de recursos en el CDN, por lo que debe deshabilitarse, y preocuparse de la optimización de manera temprana.

Al usar un CDN en donde los recursos se distribuyan por otro nombre de dominio, se debe configurar CORS correctamente también.

Referencias

MDN contributors (2020, mayo 28). Subresource Integrity. MDN web docs. https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity

T4 Labs Inc (2020, abril 30). CDN Market Share, T4. https://www.t4.ai/industry/cdn-market-share

Khandelwal, S. (2019, octubre 23). New Cache Poisoning Attack Lets Attackers Target CDN Protected Sites. The Hacker News. https://thehackernews.com/2019/10/cdn-cache-poisoning-dos-attack.html

Wikipedia (2020, julio 27). Minification (programming). Wikipedia. https://en.wikipedia.org/wiki/Minification_(programming)

Colaboradores de MDN (2019, marzo 23). Atributos de configuración CORS. MDN web docs. https://developer.mozilla.org/es/docs/Web/HTML/Atributos_de_configuracion_CORS


Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

%d