Protocolo Websocket
Uno de los obstáculos para crear aplicaciones web manejando contenidos cambiando rápidamente (pensar en actualizaciones de estado y mensajes de chat), es el modelo de petición/respuesta HTTP. En la carrera por las micro-optimizaciones sobre este tipo de comportamiento, los sitios acaban chocando contra una pared en el cual el navegador debe sondear continuamente al servidor por actualizaciones. En otras palabras, el navegador siempre inicia la petición, ya sea GET, POST o algún otro método. Los WebSockets abordan esta limitación en el diseño de HTTP proporcionando un canal de comunicación bidireccional, también conocido como full-duplex. Las conexiones URL de WebSocket utilizan los esquemas ws:// o wss://, este último para conexiones sobre SSL/TLS.
Una vez el navegador establece una conexión WebSocket hacia un servidor, tanto el servidor como el navegador pueden iniciar una transferencia de datos a través de la conexión. Antes de los WebSockets, el navegador malgastaba ciclos de CPU o ancho de banda para sondear periódicamente al servidor en busca de nuevos datos. Con WebSockets, los datos enviados desde el servidor activan un evento del navegador. Por ejemplo, en lugar de comprobar cada dos segundos si hay un nuevo mensaje de chat, el navegador puede utilizar un enfoque basado en eventos los cuales se activan cuando una conexión WebSocket entrega nuevos datos desde el servidor.
La siguiente captura de red muestra el handshake utilizado para establecer una conexión WebSocket desde el navegador hacia un servidor público en ws: //echo.websocket.xyz
GET /?encoding=text HTTP/1.1
Host: echo.websocket.xyz
Connection: keep-alive, Upgrade
Sec-WebSocket-Version: 13
Origin:http: //websocket.xyz
Sec-WebSocket-Key: ZIeebbKKfc4iCGg1RzyX2w==
Upgrade: websocket
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Accept: YwDfcMHWrg7gr/aHOOil/tW+WHo=
Server: Kaazing Gateway
Date: Thu, 22 Mar 2012 02:45:32 GMT
Access-Control-Allow-Origin:http: //websocket.xyz
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
El navegador envía un valor aleatorio de 16 bytes en Sec-WebSocket-Key. El valor está codificado en base64 para sea aceptable para HTTP. En el ejemplo anterior, la representación hexadecimal de la clave es 64879e6db28a7dce22086835473c97db. En la práctica sólo es necesario recordar la representación codificada en base64.
El navegador también debe enviar la cabecera Origin. Esta cabecera no es específica de los WebSockets. “Origin” indica el contexto de navegación en el cual se crea la conexión WebSockets. En el ejemplo anterior, el navegador visitó http: //websocket.xyz/ para cargar la demo. La conexión WebSockets se está realizando hacia un “Origin” diferente, ws: //echo.websocket.xyz/. Esta cabecera permite el navegador y el servidor se acuerden sobre cuales “Origins” pueden mezclarse cuando se conectan a través de WebSockets.
Sec-WebSocket-Version indica la versión de WebSockets a utilizar. El valor actual es 13. Anteriormente era el 8. Como ejercicio para seguridad, nunca está de más ver como responde un servidor a valores no utilizados (del 9 al 11), valores negativos (-1), valores más altos (sería el 14 en este caso), valores potenciales de desbordamiento de enteros (2 ^32 , 2 ^32 +1, 2 ^64 , 2 ^64 +1), etc. Haciendo esto se estaría probando el propio código del servidor web en lugar de la aplicación web.
El significado de las cabeceras de respuesta del servidor es el siguiente.
Sec-WebSocket-Accept es la respuesta del servidor hacia la cabecera del reto del navegador, Sec-WebSocket-Key. La respuesta reconoce el reto combinando Sec-WebSocket-Key con un GUID definido en el RFC 6455. Este acuse de recibo es verificado por el navegador. Si los valores de la clave/aceptación de ida y vuelta coinciden, se abre la conexión. Caso contrario el navegador rechazará la conexión. El siguiente ejemplo demuestra la verificación de la clave utilizando herramientas de línea de comandos disponibles en la mayoría de los sistemas tipo Linux. El hash SHA-1 de Sec-WebSocket-Key y GUID concatenados coincide con el hash codificado en Base64 de la cabecera Sec-WebSocket-Accept calculado por el servidor.
{Sec-WebSocket-Key}{WebSocketKeyGUID}
ZIeebbKKfc4iCGg1RzyX2w==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
$ echo -n 'ZIeebbKKfc4iCGg1RzyX2w==258EAFA5-E914-47DA-95CA-C5AB0DC85B11' | shasum -
6300df70c1d6ae0ee0aff68738e8a5fed5be587a -
$ echo -n 'YwDfcMHWrg7gr/aHOOil/tW+WHo=' | base64 -D | xxd
0000000: 6300 df70 c1d6 ae0e e0af f687 38e8 a5fe c..p........8...
0000010: d5be 587a
Este handshake de reto/respuesta está diseñado para crear una conexión única e impredecible entre el navegador y el servidor. Podrían surgir varios problemas si las claves de desafío fueran secuenciales, por ejemplo, 1 para la primera conexión, luego 2 para la segunda; o basadas en el tiempo, por ejemplo, “epoch time” en milisegundos. Una posibilidad son las condiciones de carrera; el navegador debería asegurarse de la clave de reto 1 no sea utilizada por dos solicitudes intentendo establecer una conexión al mismo tiempo. Otra preocupación es evitar las conexiones WebSockets se utilicen para ataques cruzados de protocolo.
Los ataques cruzado de protocolo son un viejo truco en el cual el tráfico de un protocolo se dirige hacia el servicio de otro protocolo con el fin de falsificar los comandos. Esto es lo más fácil de explotar con protocolos basados en texto. Por ejemplo recordar la primera línea de una petición HTTP el cual contiene un método, un URI y un indicador de versión:
GEThttp: //web.site/HTTP/1.0
El correo electrónico utiliza otro protocolo basado en texto, SMTP. Imaginar un navegador web con un objeto XMLHttpRequest (XHR), el cual no impone ninguna restricción sobre el método HTTP o el destino. Un spammer inteligente podría tratar de atraer a los navegadores hacia una página web el cual utiliza un objeto XHR para conectarse hacia un servidor de correo intentando una conexión como:
EHLOhttps: //correo.server:587 HTTP/1.0
O si se pudiera dar al XHR un método completamente arbitrario, un ciberatacante trataría de meter en este una orden de entrega de correo electrónico completa. El resto de la petición, incluyendo las cabeceras añadidas por el navegador, no importarían para el ataque:
EHLO%20correo.server:587%0a%0dMAIL%20FROM:<alice @social.
red>%0a%0dRCPT%20TO:<bob @social.red>%0a%0dDATAspamspamsp
am%0a%0d.%0ahttps: //correo.server:587 HTTP/1.1
Host: correo.server
La sintaxis no siempre es 100% correcta para los ataques entre protocolos; sin embargo, este tipo de ataques surgen debido a errores en la implementación (el navegador permite conexiones hacia puertos TCP con protocolos no HTTP ampliamente establecidos como el 25 o el 587, el navegador permite el objeto XHR envíe contenido arbitrario, el servidor de correo no aplica estrictamente la sintaxis).
Los WebSockets son más versátiles comparados con el objeto XHR. Al ser un protocolo orientado a mensajes puede transferir contenido binario o de texto, son un candidato principal para intentar ataques entre protocolos contra cualquier cosa, desde servidores SMTP hasta incluso protocolos binarios como SSH. El reto/respuesta Sec-WebSocket-Key y Sec-WebSocket-Accept garantiza un navegador adecuado se conecte hacia un servidor WebSocket válido en lugar de a cualquier tipo de servicio (por ejemplo, SMTP). La intención es evitar los ciberatacantes puedan crear páginas web provocando el navegador de la víctima envíe spam o realice alguna otra acción contra un servicio el cual no sea WebSocket; así como evitar que ataques como la inyección HTML entreguen cargas útiles los cuales puedan convertir una vulnerabilidad de Twitter en un generador de spam de gran volumen. El reto/respuesta evita el navegador sea utilizado como relevo para ataques contra otros servicios.
La cabecera Sec-WebSocket-Protocol (no presente en el ejemplo) proporciona a los navegadores información explícita sobre el tipo de datos a canalizar a través de un WebSocket.
Será una lista de protocolos separada por comas. Esto le da al navegador la oportunidad de aplicar decisiones de seguridad para protocolos comunes, en lugar de tratar con un flujo de datos opaco con implicaciones desconocidas para la configuración de seguridad o privacidad del usuario. Los marcos de datos pueden ser enmascarados con una operación XOR utilizando un valor aleatorio de 32 bits elegido por el navegador. Los datos se enmascaran para evitar modificaciones involuntarias por parte de dispositivos intermediarios como los proxies. Por ejemplo un proxy caché podría devolver incorrectamente datos antiguos para una solicitud, o un proxy el funcione mal podría manipular una trama de datos. Observar la especificación no utiliza el término encriptación, pues ese no es el propósito ni el efecto del enmascaramiento. La clave de enmascaramiento está incrustada dentro de la trama de datos si afecta - abre para que cualquier intermediario lo vea. Las conexiones TLS proporcionan encriptación con cifrados de flujo como RC4 o AES en modo CTR. Utilizar wss: // para conseguir un cifrado fuerte para la conexión WebSocket. De la misma manera se confiaría en https: // para los enlaces a las páginas de acceso o, preferiblemente, toda la aplicación.
Fuentes:
https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
https://www.rfc-editor.org/rfc/rfc6455
Sobre el Autor
Alonso Eduardo Caballero Quezada - ReYDeS
Instructor y Consultor Independiente en Ciberseguridad
WhatsApp: https://wa.me/51949304030
Correo Electrónico: ReYDeS@gmail.com
Twitter: https://twitter.com/Alonso_ReYDeS
Youtube: https://www.youtube.com/c/AlonsoCaballero
LinkedIn: https://pe.linkedin.com/in/alonsocaballeroquezada/