Contextualizando
En mayo de 1996, mientras Tom Cruise estrenaba Mission: Impossible, se publicaba el RFC 1945 que definía la versión 1.0 del protocolo HTTP. Se trataba de un protocolo muy sencillo, sin demasiadas pretensiones más allá de transmitir (hiper)texto, y algún que otro gif animado (sí, llevan con nosotros desde 1987).
La revisión 1.1 del protocolo, vio la luz durante el año 1999 (se estrenaba Matrix) y nos acompañó durante varios -demasiados- años. Se introdujeron varias mejoras en múltiples implementaciones, y con esta revisión 1.1, HTTP se convertiría en el protocolo estrella de Internet.
Ya no solo se utilizaba para servir documentos HTML y ficheros de imágenes. Desde streaming multimedia en directo, o servir como soporte estándar para la comunicación entre máquinas (REST!), multitud de servicios se han basado en el protocolo HTTP para poder dar servicio y no hay indicios de que esto vaya a cambiar sustancialmente en los próximos años.
El caso es que el concepto sigue siendo el mismo; a pesar de los múltiples matices y excepciones, HTTP es un protocolo “sin estado”, basado en el paradigma petición y respuesta:
- Un cliente (generalmente un navegador web), crea una petición. Esta petición request se compone de cabeceras y un cuerpo en un formato determinado (json, urlencoded, xml, etc).
- El servidor recibe la petición, y genera una respuesta, que a su vez estará compuesta de cabeceras y un cuerpo en un formato determinado (html, texto, json, información en binario, etc).
Y ya está, no hay mucho más (bueno están los websockets, pero vamos a dejarlos tranquilos de momento). Por mucho que encapsulemos la lógica detrás de una API de cliente, desde una torpe implementación cutre con 4 variables globales, hasta un contenedor de estados inmutables con Redux, el concepto sobre el que se sostiene todo es una comunicación muy simple basada en peticiones-respuestas sobre HTTP.
La llegada de HTTP/2, ha supuesto un rediseño en la capa de transporte, pero la esencia se ha mantenido más o menos igual. Para un desarrollador web el cambio a HTTP/2 no supone ningún cambio radical a la hora de plantear una arquitectura, se ha respetado la manera de funcionar de HTTP/1.1. Se ha rediseñado la manera en la que se realiza la comunicación entre cliente y servidor, pero tanto los verbos HTTP (GET, POST, PATCH, etc…) como la naturaleza pregunta-respuesta de HTTP, sigue siendo igualmente válida en la versión 2 de HTTP. De la misma manera, las cabeceras tanto en las peticiones como en las respuestas se utilizan de la misma manera; lo único que cambia es que en HTTP se utiliza HPACK (recogido en el RFC 7541). HTTP/2 ha conseguido añadir una enorme mejora de rendimiento, prácticamente sin alterar el cómo desarrollamos web; pero esta sería otra historia que no vamos a contar en este artículo.
La enorme funcionalidad que nos aporta HTTP, a pesar (o gracias a) de su simplicidad, se apoya en la variedad de cabeceras que tenemos disponibles tanto en las peticiones como en las respuestas. Existen cerca de 30 tipos de cabeceras con las que se puede construir una petición HTTP y más de 40 cabeceras distintas con las que un servidor puede responder a esa petición.
Aunque ese número de cabeceras puede crecer hasta el infinito, ya que cualquiera puede crear sus propias cabeceras, tanto en las peticiones como en las respuestas 😈.
Generalmente, la cabeceras inventadas custom, utilizan el prefijo “X-”, y muchas de ellas, como veremos a continuación, han sido adoptadas globalmente.
Como desarrolladores web, conocer la mayoría de las cabeceras HTTP y su funcionalidad supone una enorme ventaja. Seguramente nos evitará reinventar la rueda en varias ocasiones, consiguiendo además una rueda mucho más eficiente, completa y mejor documentada de la que las que nosotros podamos diseñar. Aun así, es relativamente frecuente ver cómo se siguen construyendo, por ejemplo, sistemas de cache en clientes http que ignoran por completo “Etag” o un “Last-Modified”… Inevitablemente la rueda sigue reinventándose una y otra vez.
El objetivo de este artículo consiste en llegar a conocer cómo podemos aprovecharnos de las cabeceras HTTP en las respuestas; más concretamente en cómo añadir una capa de seguridad extra a nuestras aplicaciones web, utilizando únicamente estas cabeceras de respuesta.
Securizando
Primero planteamos una hipótesis. Dando por hecho que nuestros servidores web se encuentran perfectamente aislados en bunkers a 300 metros bajo el océano; que esos bunkers cuentan con seguridad biométrica que los hacen virtualmente inaccesibles a quien no esté autorizado; dando por hecho que nuestra aplicación web está desplegada en una DMZ, detrás de carísimos firewalls que justifican el puesto de cualquier responsable de seguridad de cualquier empresa; dando por hecho que los certificados SSL de 2048 bits hacen que los candados sean verde fosforito en todos los navegadores presentes y futuros.
Entonces es donde un programador se deja input de usuario sin filtrar; un pedazo de javascript furtivo que se ejecutará cuando no tenía que ejecutarse; un iframe transparente que hace que pulses “Me gusta” en una página de Facebook que no quieres que la gente sepa que te gusta.
La seguridad no es algo binario. Nada es 100% seguro o 100% inseguro. Para empezar es importante saber qué se está securizando y también es importante conocer el coste de asignar más seguridad a lo que se esté securizando. La clave está en llegar a un equilibrio coste-seguridad razonable.
Desde el punto de vista de un desarrollador web, es importantísimo tener presentes el top ten de OWASP. Es responsabilidad nuestra desarrollar aplicaciones teniendo en cuenta la seguridad desde el principio, y no delegando 100% en la capa de infraestructuras, ya que su responsabilidad no abarca el 100% de los peligros que pueden amenazar nuestra aplicación.
Este artículo pretende dar a conocer qué nos puede aportar HTTP en lo referido a la seguridad. Existen varias cabeceras de respuesta HTTP que han sido diseñadas para evitar ciertos ataques muy comunes en el universo web actual. Sin embargo, utilizar correctamente las cabeceras no convertirá automáticamente tu sitio web en seguro. No sustituyen a firewalls perimetrales ni a la máxima de “NUNCA CONFIAR EN INPUT DE USUARIO”. Utilizar correctamente este tipo de cabeceras añade un grado extra de seguridad con un coste relativamente pequeño.
Vamos a enumerar, describir e intentar descubrir qué nos puede aportar que nuestro servidor web responda con alguna de estas cabeceras (o todas) en las respuestas HTTP.
Cabeceras HTTP
Content-Security-Policy
La cabecera Content-Security-Policy (CSP) es la madre de todas las cabeceras en lo relativo a seguridad. Fue una iniciativa de Mozilla, que desplegaron en 2011 con Firefox 4 y que ha sido adoptado y evolucionado por el resto de fabricantes. Añade una capa extra de seguridad para prever, entre otros, ataques de Cross Site Scripting (XSS) o de inyección de datos.
Una página web está compuesta por multitud de elementos externos: imágenes, hojas de estilo, ficheros de javascript, vídeos, otros elementos HTML… Hablando en un contexto de seguridad, la carga de todos estos recursos desde el mismo origen (same-origin), nos aseguraría que la página es 100% segura; al menos en lo relativo a ataques XSS.
Pero cualquier web actual utiliza multitud de recursos desde servidores externos: cargamos jquery desde un cdn, la tipografía desde fonts.gstatic.com o el mismo script de Google Analitycs. Esta funcionalidad, aunque nos permite una enorme flexibilidad, es una de las principales puertas de entrada de potenciales ataques a nuestras aplicaciones.
Una opción podría ser que los navegadores cerraran las puertas explícitamente a la carga de cualquier recurso externo, salvo que el servidor web nos indique lo contrario. La única pega es que se rompería Internet… Es algo que habría que haber previsto en 1998 o 1999.
Utilizar CSP nos posibilita eso justamente. El desarrollador puede optar por hacer listas blancas de determinados recursos de manera que todo lo que se ejecute en la web, salvo que esté especificado, será bloqueado por el navegador.
Cada cabecera “Content-security-Policy”, deberá contener una o varias directivas en cada una de las peticiones, y se aplicará a la página enviada en esa petición. Vamos a ver las directivas más comunes para hacernos una idea de que puede ofrecernos:
- child-src: esta directiva establece una lista blanca de dominios para workers y contenidos de tipo frame.
- connect-src: limita las conexiones vía XHR, WebSockets o de tipo EventSource.
- font-src: define los orígenes autorizados para la descarga de fuentes.
- form-action: especifica los actions a los que puede apuntar un formulario.
- frame-ancestors: limita qué dominios pueden contener la página actual en un frame o iframe. (viene a sustituir la cabecera X-Frame-Options, que más tarde se describe en este artículo).
- img-src: limita los orígenes de elementos tipo img.
- media-src: similar a img-src, pero para elementos de tipo video y audio.
- object-src: de nuevo, limita elementos de tipo object (como flash u otros plugins)
- script-src: establece la lista blanca para scripts JS.
- style-src: en este caso, se limita la carga de hojas de estilo.
- default-src: si se especifica, se establecerá una lista blanca por defecto en la carga de la página actual. No obstante, si incluimos una directiva de un objeto concreto, será este valor el que prevalezca.
- report-uri: si está presente y se produce una violación del CSP actual, el navegador informará a la URI especificada mediante una petición .
Cada una de estas directivas puede tener uno o varios valores asignados (separados por espacio). Se pueden especificar dos tipos de valores:
- Literales: referentes a nombres de URIs de manera bastante flexible
- http://*example.com
- example.com
- *://*.example.com
- Palabras reservadas: CSP tiene en cuenta 4 palabras reservadas específicas, que deberán incluirse entrecomilladas:
- ‘none’: En este caso no se permitirá ningún objeto al que haga referencia la directiva.
- ‘self’: Se haría referencia al dominio de la página actual (no a su subdominio).
- ‘unsafe-inline’: Se permitiría javascript y css inline (denegado por defecto).
- ‘unsafe-eval’: Permitiría utilizar mecanismos de evaluación de js (eval, setTimeout, etc)
Actualmente existen 2 especificaciones para CSP, aunque por el grado de adopción actual es aconsejable utilizar la primera revisión.
- Content-Security-Policy 1.0: Implementado por la mayoría de los navegadores.
- Content-Security-Policy Level 2: Añade ciertas directivas a la primera versión, aunque todavía no está soportado por IE/Edge.
Para aprender más sobre cómo utilizar esta cabeceras sería aconsejable echar un ojo a https://content-security-policy.com/, además de a este artículo en html5rocks, y nunca está de más consultar el artículo sobre CSP en la MDN.
X-Content-Type-Options
Esta cabecera fue creada por la versión 8 de Internet Explorer diseñada únicamente para obligar a los navegadores a aceptar la cabecera Content-Type que acompañe a cada petición http.
Los navegadores web implementan por defecto ciertos algoritmos que tratan de descubrir qué tipo de fichero se está descargando y, si el mimetype no coincide con el especificado en la cabecera “Content-type”, actúa en consecuencia.
Cabecera “X-Content-Type-Options” en el mundo real | |
Sitio web | Cabecera |
flickr.com | x-content-type-options:nosniff |
facebook.com | x-content-type-options:nosniff |
spotify.com | X-Content-Type-Options:nosniff |
Así pues, si una imagen tiene una cabecera “Content-type: image/jpeg”, pero la imagen que se descarga es un png, el navegador será capaz de obviar la cabecera y mostrar el png. Aunque puede resultar algo práctico en la mayoría de los casos, el navegador podría llegar a interpretar HTML cuando se pretende mostrar un fichero de texto, o Javascript cuando el servidor esté devolviendo plain/text.
La cabecera “x-content-type-options” tiene una única directiva posible: nosniff. Si está presente en una petición, los navegadores obedecerán siempre la cabecera “Content-type” sin intentar descubrir el contenido.
Aquí está el anuncio de Microsoft de ésta funcionalidad y otras novedades que IE8 aportó en su día.
X-Frame-Options
Esta cabecera obliga a los navegadores que la implementen a comprobar ciertos requisitos antes de renderizar un objeto <iframe>, <frame> u <object> a partir de una determinada URL.
Los valores que puede tener esta cabecera son los siguientes:
- X-Frame-Options: DENY – Evitará que el contenido sea incrustado en algún iframe, frame u object
- X-Frame-Options: SAMEORIGIN – Solo se permitirá el renderizado desde HTML que a su vez compartan el origen
- X-Frame-Options: ALLOW-FROM https://example.com/ – Se permitirá el renderizado desde determinadas URIs. (no soportado en Chrome!)
Evitar que nuestro contenido sea servido como iframes, además de ofrecer un mejor control sobre dónde y cómo se muestra nuestro contenido, es muy útil de cara a evitar ataques de clickjaking (básicamente consiste en técnicas que consiguen que hagas click donde no querías hacer click… por ejemplo para conseguir “Me gusta” fraudulentos en facebook).
Sitio Web | Cabecera |
google.com | x-frame-options:SAMEORIGIN |
facebook.com | x-frame-options:DENY |
bilbao.eus | X-Frame-Options:SAMEORIGIN |
Este tipo de cabeceras es soportado por la mayoría de navegadores web como puede observarse de nuevo en caniuse.com, aunque actualmente está obsoleta, en favor de la directiva “frame-ancestors” contemplada en “Content-Security-Policy Level 2”. Sin embargo, es importante no olvidarla, ya que existe un considerable número de usuarios utilizando navegadores que no implementan CSP level 2.
Si alguna vez os toca desarrollar una de esas aplicaciones para Facebook (todavía queda alguna), no olvidéis que vuestro desarrollo será ejecutado dentro de un <iframe>. Si no os funciona, echad un ojo a esta cabecera, no vaya a ser que se esté enviando esta cabecera con un DENY y justamente no funcione por eso (le ha pasado a un amigo).
X-XSS-Protection
Esta cabecera tiene su origen en el denominado “XSS Filter”, funcionalidad que traía incluida la versión 8 de Internet Explorer y que trataba de evitar ataques indirectos (o reflejados) de Cross-site Scripting. Chrome y Safari implementaron “XSS Auditor” con el mismo objetivo.
Ambos casos tiene un comportamiento similar, y en ambos casos es conocido que no se ofrece una seguridad 100% frente a ataques de tipo XSS; se trata únicamente de una capa de protección adicional frente a los ataques más comunes.
Aunque puede tratarse de una protección obsoleta, que no ofrece nada que no ofrezca una cabecera “Content-security-policy” correctamente configurada, es importante tenerla en cuenta, ya que es soportada en un importante número de navegadores.
El servidor podrá devolver 2 posibles valores en esta cabecera de respuesta:
- 0 Protección desactivada en el navegador. No se buscan posibles ataques XSS.
- 1 El navegador busca ataques XSS indirectos; si los encuentra, eliminará las partes maliciosas. (Éste sería el comportamiento por defecto en el navegador).
- 1;mode:block Si el navegador encuentra un ataque XSS, y tiene esta directiva especificada, no se mostrará la página en absoluto.
Cabecera “X-XSS-Protection” en el mundo real | |
Sitio web | Cabecera |
google.es | x-xss-protection:1; mode=block |
facebook.com | x-xss-protection:0 |
tumblr.com | X-XSS-Protection:1; mode=block |
El comportamiento por defecto de los navegadores que implementan esta funcionalidad consiste en activar la protección por defecto; es decir si el servidor no devuelve la cabecera, se da por hecho que el valor del filtro es “activado”; Si se detecta un ataque XSS indirecto, se procederá a eliminar únicamente la parte “inyectada”.
Parece ser que eso no es demasiado buena idea; como puede verse aquí o aquí, además de existir maneras de saltarse la protección también se ha documentado la manera de inyectar código malicioso aprovechando esta el propio filtro. Así pues, el valor que se recomienda utilizar es bien 0 si se está 100% seguro de que no exponemos nuestro sitio web a ataques XSS, o bien habría que especificar 1;mode:block para evitar vulnerabilidades adicionales.
Para saber más al respecto recomiendo leerse este artículo en XSS Jigsaw.
Strict-Transport-Security
HTTP Strict Transport-Security (HSTS), es un mecanismo recogido en el RFC 6797 cuyo objetivo es obligar al navegador a utilizar HTTPS en todas las conexiones futuras. La cabecera que envía el servidor establece además la cantidad de tiempo que el navegador deberá usar una conexión segura contra ese nombre de dominio.
Este mecanismo utiliza la cabecera “Strict-Transport-Security” seguida por la directiva “max-age” que determina cuántos segundos debe recordar acceder a ese dominio utilizando únicamente https.
El RFC recoge también la directiva opcional “includeSubdomains”, con el fin de informar al cliente que debe aplicar https a todos los subdominios del nombre de dominio del host.
Cabecera “Strict-Transport-Security” en el mundo real | |
Sitio web | Cabecera |
github.com | Strict-Transport-Security:max-age=31536000; includeSubdomains; preload |
meneame.net | Strict-Transport-Security:max-age=15638400 |
iberdrola.com | Strict-Transport-Security:max-age=157680000 |
Los casos de uso que se pretenden evitar con esta cabecera podrían ser, por ejemplo, un bookmark mal almacenado (sin https), direcciones escritas manualmente sin el https o incluso ataques más sofisticados que fuercen una redirección a una URL no segura que pudiera exponer datos sensibles del usuario.
En sitios relativamente grandes, es habitual encontrarse la directiva “preload”. Esta directiva no está presente en el RFC, y responde a una iniciativa de Google para que los navegadores dispongan de una lista precargada de dominios que implementan HSTS. Este listado está soportado por las últimas versiones de los principales navegadores y cualquier servidor web puede optar a estar en este listado precargado, siempre que se cumplan ciertas condiciones.
No obstante, tampoco vamos a extendernos demasiado en esta cabecera ya que en mayo de 2015, Iker Sagasti dedicó una entrada exclusivamente hablando de HSTS.
Public-Key-Pins
Esta cabecera HTTP responde al RFC 7469, donde se explica HTTP Public Key Pinning (ó HPKP), que trata de ofrecer un mayor nivel de protección frente a ataques de Man In The Middle, llegando incluso a evitar casos en los que se compromete a la entidad certificadora.
El concepto es relativamente simple:
- La cabecera “Public-key-Pins”, en la directiva pin-sha256 incluye la huella SPKI de la clave pública de la cadena de certificados del dominio.
- Es habitual y recomendable disponer de al menos un pin de backup, de manera que si debemos renovar el certificado los clientes que tienen un pin antiguo sigan teniendo acceso mediante este segundo pin.
- El pin se almacenará durante el tiempo (en segundos) especificado en la directiva max-age de la cabecera.
- Si se encuentra la cabecera includeSubDomains, este pin se aplicará para todos los subdominio del nombre de dominio.
- En las posteriores conexiones desde el navegador, dentro del tiempo de vida del pin, se comprobará que en al menos uno de los certificados en la cadena de certificados HTTPS, se encuentre una clave pública cuya huella coincida con la almacenada.
- Existe una última directiva report-uri, donde el servidor informa al navegador sobre la URL en la que reportar cualquier error en la validación.
Cabecera Public-Key-Pins en el mundo real | |
Sitio web | Cabecera |
github.com | Public-Key-Pins:max-age=5184000; pin-sha256=»W…=»; pin-sha256=»R..=»; pin-sha256=»k…=»; pin-sha256=»K…=»; pin-sha256=»I…=»; pin-sha256=»…=»; pin-sha256=»…=»; includeSubDomains |
support.mozilla.org | Public-Key-Pins:max-age=1296000; pin-sha256=»r…=»; pin-sha256=»W…=»; |
Resulta difícil encontrar sitios que hagan uso de esta funcionalidad, ya que de momento su uso no está muy extendido. Es una funcionalidad relativamente nueva (Abril de 2015), implementada únicamente en Chrome y Firefox, y que mal utilizada o entendida puede dejar a un gran número de usuarios bloqueados de tu sitio web hasta que caduque el max-age.
La gente de Netcraft explica aquí y aquí todo lo relativo a HPKP; también es interesante conocer la mala experiencia que parece tuvo el departamento de sistemas de Smashing Magazine, así como este artículo donde se expone cuestiona el futuro de esta funcionalidad.
En conclusión: antes de implementar HPKP en tus servidores, primero es necesario entender bien el concepto y valorar los posibles contras que podríamos encontrarnos.
Server
Por último presentamos esta cabecera sin demasiada relevancia directa a nivel de seguridad. El objetivo de esta cabecera es informar al usuario de qué tecnología hace funcionar el servidor web al que se está accediendo. Y cuando decimos usuario, decimos casi exclusivamente bots que recogen información de servidores en Internet para publicar gráficas de uso de servidores web[1][2].
Existen varias cabeceras con propósitos similares que podremos ir encontrando en las respuestas HTTP ahí fuera: X-Powered-By, X-Runtime, X-Version ó X-AspNet-Version… son versiones “custom” que distintos fabricantes han ido desplegando en sus productos.
Cabecera Server en el mundo real | |
Sitio web | Cabecera |
google.com | server: gws |
bing.com | Server: Microsoft-IIS/8.5 |
elcorreo.com | Server: Apache |
php.net | X-Powered-By: PHP/5.6.27-0+deb8u1 |
youtube.com | server: YouTubeFrontEnd |
Evitar enviar la cabecera Server o cualquiera de sus primas hermanas simplemente nos ahorra un poco ancho de banda, evitando enviar unos pocos bytes en cada petición. También se contribuirá a una mayor inexactitud en las gráficas que buscamos cuando queremos encontrar argumentos ad-populum a la hora de defender nuestro servidor web favorito.
Si bien es cierto que en determinados contextos puede ser el origen de ciertos ataques automatizados… y aunque no deberíamos usar la ocultación como medida de seguridad, si mantienes en producción versiones inseguras de servidores web (y es algo que ocurre a menudo 😱), sí que sería buena idea esconder este tipo de cabeceras… O bueno, actualizar es otra opción igualmente válida.
Conclusión
Haciendo una comprobación rápida de los sitios web más visitados en Euskadi nos encontramos con una muy baja implantación de este tipo de cabeceras.
No pretendemos generar ningún tipo de alarmismo, ni consideramos que una web sea más insegura por carecer de este tipo de cabeceras. No obstante, la ausencia de esta configuración en las respuestas HTTP quizás pueda ser interpretada como un síntoma de que la seguridad no está siendo muy tenida en cuenta en los principales portales web de nuestro entorno. La correcta configuración de nuestros portales web nos ofrece una capa extra de seguridad con un esfuerzo relativamente pequeño.
Para saber más y mejor:
Queremos tu opinión :)