Cómo evitar que tus archivos JavaScript y CSS sean manipulados

12 de enero de 2016

Las versiones más modernas de los mejores navegadores incluyen una funcionalidad para que los sitios web puedan controlar cómo se carga su código JavaScript y así evitar que sea modificado por usuarios maliciosos.

Esta nueva funcionalidad, llamada "Integridad de Recursos" (en inglés, "SRI" o Subresource Integrity), permite a los sitios web incluir código JavaScript que no se ejecuta si su contenido ha sido modificado.

La gran ventaja que aporta es que puedes usar CDNs (Content Delivery Networks) para servir el contenido de tus sitios lo más rápido posible, pero al mismo tiempo asegurarte que los archivos no han sufrido ninguna modificación.

Usar la "integridad de recursos" es muy sencillo, tal y como muestra el siguiente ejemplo:

<script src="https://code.jquery.com/jquery-2.1.4.min.js"
integrity="sha384-R4/ztc4ZlRqWjqIuvf6RX5yb/v90qNGx6fS48N0tRxiGkqveZETq72KgDVJCp2TC"
crossorigin="anonymous"></script>

La idea consiste en añadir al elemento <script> una firma digital (en inglés, cryptographic hash) de sus contenidos. Cuando el navegador cargue la página, descargará los contenidos, calculará la firma digital y comprobará que su valor coincida con el del atributo integrity. Si no coinciden, el código JavaScript no se ejecuta. Por lo tanto, se trata de una medida de seguridad sencilla pero eficaz para protegerte frente a CDNs que han sido atacadas y frente a administradores de sistemas con malas intenciones.

La función utilizada para generar la firma digital debe ser lo suficientemente segura y resistente a las colisiones, de manera que cualquier mínimo cambio en el contenido genere una firma totalmente diferente y única.

Un detalle importante para utilizar esta funcionalidad es que la CDN debe soportar CORS (Cross-Origin Resource Sharing). Por eso en el ejemplo anterior se incluye el atributo crossorigin para forzar la activación de CORS al cargar los contenidos.

El valor anonymous del atributo crossorigin significa que el navegador debe ignorar cualquier cookie o información de autenticación que el usuario pueda tener asociado con ese dominio. Además de mejorar el rendimiento (al enviar menos información en la petición) esto impide que se filtre información entre diferentes dominios.

Sintaxis del atributo integrity

Como puedes ver en el ejemplo anterior, el atributo integrity no solo contiene el valor de la firma digital, sino que también incluye la función utilizada para generarlo. De hecho, puedes añadir varias firmas digitales que utilicen diferentes funciones (separa cada firma con una coma):

<script src="https://code.jquery.com/jquery-2.1.4.min.js"
integrity="sha384-R4/ztc4ZlRqWjqIuvf6RX5yb/v90qNGx6fS48N0tRxiGkqveZETq72KgDVJCp2TC
sha256-8WqyJLuWKRBVhxXIL1jBDD7SDxU936oZkCnxQbWwJVw="
crossorigin="anonymous"></script>

El primer uso de las firmas múltiples es que puedes utilizar firmas de diferentes niveles de seguridad (bajo para navegadores antiguos y más alto para los navegadores modernos). También puedes usar las firmas múltiples cuando una misma URL acoge a varios scripts diferentes, como por ejemplo cuando el navegador usa la negociación de contenido.

Recuperándose de los errores

Si el valor del atributo integrity no coincide con el del contenido descargado por el navegador, el código JavaScript no se ejecuta. En la práctica, esto significa que los usuarios se encontrarán en un sitio web en el que no funciona nada.

La solución a este problema es muy sencilla. En primer lugar, guarda en tu sitio web una copia de los contenidos que estás sirviendo a través de la CDN. Después, utiliza algo de código JavaScript para comprobar si se han cargado los contenidos externos. Si no, carga los archivos desde tu propio servidor.

En el ejemplo anterior se está cargando la librería jQuery. Así que el código que comprueba si se ha cargado puede ser tan sencillo como el siguiente:

<script>window.jQuery || /* cargar aquí nuestros archivos */;</script>

Este código comprueba si se ha definido el objeto jQuery y en caso contrario, insertaría una etiqueta para cargar el archivo JavaScript desde el propio servidor.

Ten en cuenta que muchos scripts se actualizan continuamente, sobre todo los que no definen un número de versión. En estos casos, es mejor fijar la versión exacta que se carga desde la CDN y no usar archivos que incluyen versiones genéricas como latest.

¿HTTP o HTTPS?

La "integridad de recursos" funciona tanto con HTTP como con HTTPS. Si usas HTTP, el navegaodr puede comprobar si el valor del atributo integrity es correcto, pero no es capaz de defenderde de los ataques que se produzcan sobre ese acnal de comunicación no seguro. Esto por ejemplo puede hacer que un atacante elimine el atributo integrity.

Así que, como siempre, es aconsejable que utilices HTTPS para que al combinarlo con el atributo integrity los usuarios disfruten del mayor nivel posible de confidencialidad y seguridad.

Soporte para hojas de estilos

Todavía se está trabajando para ampliar esta funcionalidad a otros tipos de recursos diferentes a los archivos JavaScript. No obstante, ya puedes utilizarlo también para las hojas de estilos CSS. Simplemente añade al atributo integrity al elemento <link> de tus páginas.

Prueba hoy mismo la integridad de recursos

Visita el sitio srihash.org para comprobar el soporte de tu navegador y para echar un vistazo a los ejemplos disponibles. En este sitio también puedes generar las firmas digitales y comprobar si tus CDN soportan esta funcionalidad.

También está disponible en varias de las CDNs y sitios más importantes, como BootstrapCDN, CloudFlare y GitHub.

Recursos adicionales

Sobre el autor

Este artículo fue publicado originalmente por Frederik Braun y ha sido traducido con permiso por Javier Eguiluz.