Una de las técnicas de hacking de páginas web más conocidas es la de Cross-Site-Scripting o XSS. Mediante esta técnica, las aplicaciones Web vulnerables pueden ser atacadas para multitud de propósitos: servir de zombies para atacar a otras Webs inundándolas con peticiones, ejecutar código en zonas de seguridad menos restrictivas (como la local) o, sobre todo, robar la identidad de los usuarios del sitio vulnerable. Ha sido utilizado tradicionalmente en combinación con correos electrónicos maliciosos para entrar en sitios Web protegidos suplantando la identidad de usuarios autenticados. En mi curso de Desarrollo de aplicaciones seguras y seguridad de .NET monto un ejemplo completo de cómo utilizarlo en un supuesto sitio de banca electrónica (muy mal hecha y vulnerable), para robar la sesión del usuario y entrar en su cuenta.

Este robo de sesiones se basa en la obtención mediante XSS de la cookie de sesión de un usuario, enviándola a un servidor bajo el control del atacante. Éste al recibirla puede ir inmediatamente al sitio web atacado y entrar como si fuese el usuario cuya identidad ha robado. En general el robo de cookies es un problema, si no tan grave como el descrito, al menos con consecuencias sobre la privacidad de los sitios Web.

No nos pensemos que este problema de seguridad es algo esotérico que no nos pasará en nuestras aplicaciones. Le pasa a cualquiera. Lo han sufrido empresas como Google, Facebook o incluso hace unos días aparecieron estos problemas en las web de las principales empresas de seguridad). Hay informes que revelan que 9 de cada 10 aplicaciones Web tienen algún problema aunque sea leve de XSS. Así que no lo subestimes.

Manejo de cookies: cliente y servidor

Generalmente en nuestras aplicaciones las cookies las escribimos con código de servidor. Éstas se envían al navegador en una cabecera HTTP y luego se reenvían, en cada nueva petición, de nuevo al servidor, donde podemos leerlas o modificarlas si así es necesario. Estas cookies sólo pueden ser accedidas desde el mismo dominio desde el que han sido creadas, por lo que un código malicioso que esté en un marco o en otra página abierta en el navegador (en otra venta o e una pestaña) no tiene acceso a las mismas. Es, en teoría, información privada y accesible sólo para nuestro dominio.

Sin embargo, por defecto, hay una forma adicional de leer las cookies enviadas por un servidor y es mediante JavaScript. El objeto document del DOM de una página web dispone de una propiedad cookie que muestra todas las cookies del dominio actual así como sus contenidos en una cadena de parejas nombre=valor separadas por punto y coma. Y es ahí precisamente en donde se rompe la seguridad. Si alguien logra inyectar código malicioso de Script en una página (por ejemplo, metiéndolo camuflado en un comentario), este código puede leer las cookies del servidor actual y enviarlas fácilmente a donde quiera. Entre estas cookies habrá datos del usuario que pueden ser privados (de ahí que no se recomiende guardar datos importantes en una cookie) y también cookies de sesión, que es en lo que se basan la mayor parte de las tecnologías Web para mantener el estado entre llamadas HTTP.

Desde hace ya muchos años existe un añadido a la cabecera HTTP para manejo de cookies que planta una restricción en cuanto a quien puede leerlas o no. Se trata de la extensión HttpOnly. Esta cadena añadida al final de la cabecera indica a los navegadores que no deben permitir la lectura de las cookies desde el lado cliente, sólo desde el servidor. Así, el código JavaScript no puede leer las cookies que lleven esta extensión a la cabecera y por lo tanto las protegemos de los ataques de XSS.

El aspecto de una cabecera de cookie normal es este:

Set-Cookie: miCookie=Dato=XXXXYYYYZZZZZ; path=/

Para evitar que las cookies puedan ser leídas desde el cliente sólo hay que añadir la extensión HttpOnly al final, así:

Set-Cookie: miCookie=Dato=XXXXYYYYZZZZZ; path=/; HttpOnly

Cómo establecer esta opción en cookies con ASP.NET

En ASP.NET es muy sencillo conseguirlo mediante código ya que la clase HttpCookie dispone de una propiedad a tal efecto. Considera el siguiente código sencillo en el que una página ASPX escribe un par de cookies que serán enviadas al navegador:

La primera de las cookies es una cookie "normal y corriente". Se establece un nombre ("Numero") y un valor (1), se añade a una cookie, se le pone un tiempo de caducidad y se manda al cliente. Por defecto no se incluye la extensión HttpOnly por lo que esta cookie va a ser accesible a código JavaScript en las páginas de nuestra aplicación.

La segunda cookie es idéntica a la anterior, pero en este caso establecemos su propiedad HttpOnly a True, de modo que se añade en la cabecera la extensión que hemos visto, y el navegador restringirá su acceso únicamente al servidor.

Podemos comprobar que está funcionando de manera muy fácil, basta con añadir un:

alert(document.cookie);

dentro de un bloque JavaScript en alguna página del sitio para ver como se abre una ventana mostrando los contenidos de las cookies a las que nuestro código JavaScript tiene acceso. Comprobaremos que la segunda cookie no aparece por ningún lado, sin embargo sigue accesible en el código de servidor. Además podemos verla en las cabeceras que se mandan al servidor, usando una herramienta de inspección como Fiddler (comentada en mi anterior post referenciado arriba)

Activación de HttpOnly para toda la aplicación

La propiedad que acabamos de ver está muy bien cuando queremos establecer esta opción sólo para algunas cookies, pero ¿y si queremos que todas las cookies sean de acceso exclusivo desde el servidor? Y especialmente si queremos que esta opción esté activa para todas las cookies de sesión, como la que mantiene el estado HTTP y que genera la propia infraestructura de ASP.NET.

La solución es establecer una opción en web.config. Dentro del nodo <system.web> podemos escribir esto:

A partir de ese momento todas las cookies serán de manejo exclusivo en el servidor, por lo que podremos despreocuparnos de utilizar la propiedad que hemos visto.

Conviene utilizar esta opción en las aplicaciones en las que recibamos datos de los usuarios que puedan contener HTML y código de JavaScript, y desde luego aparte de esto, observar siempre las buenas prácticas de seguridad en aplicaciones Web, así como la regla número 1 de seguridad que todo programador debe seguir: "jamás confíes en nada que te envíe un usuario".

Soporte de HttpOnly en los diferentes navegadores

Internet Explorer 4.0, allá por el año 1997 (casi en la prehistoria de Internet) ya soportaba esta extensión de seguridad. Así que IE es el mejor candidato para estar seguros de que funciona esta extensión. IE8 además es el único navegador que incorpora un filtro anti-XSS que si bien no es efectivo a 100% (algo imposible seguramente), hace un gran trabajo y es más de lo que ofrece la competencia, lo que demuestra la seriedad con la que en Redmond se toman la seguridad.

Firefox (o sea Mozilla, es decir, Netscape y otros que se basaban en ese motor etc...) sin embargo no lo empezó a soportar hasta su versión 2.0.0.5, en el año 2007 (diez años despues de IE). Y encima tenía un bug, por que no las retiraba del objeto XMLHttpObject, algo que se solucionó en la versión 3.0.

Opera empezó a soportarla en su versión 9.5 (Junio de 2008), por lo que todas las versiones modernas funcionan como es debido en este aspecto.

El navegador de Google, Chrome, lo soporta en el sentido de que no permite la lectura por parte de código de Script de las cookies marcadas con HttpOnly. Sin embargo soporta su escritura, algo un tanto extraño pero que mitiga mucho el efecto de un atacante malicioso.

El navegador Safari, de Apple, no tiene en cuenta para nada esta extensión y permite a JavaScript hacer lo que quiera con las cookies, lo que lo convierte en un objetivo apetecible para ataques XSS.

Así que podemos utilizarla sin demasiado problema confiando en que funcione. De todos modos lo mejor es tener en cuenta la seguridad en nuestras aplicaciones y poner contramedidas para evitar los ataques XSS  :-)

Escrito por un humano, no por una IA