
Históricamente las
aplicaciones web se han basado en un modelo cliente-servidor, donde el
cliente siempre es el primero en iniciar la comunicación con el
servidor. Con la llegada de AJAX, las peticiones podían hacerse de forma
asíncrona y el cliente no necesariamente tenia que esperar la respuesta
del servidor para poder continuar trabajando con la aplicación web, sin
embargo la comunicación tenia que ser iniciada por el cliente y si por
algún motivo, el servidor tenia nueva información que debía transferirse
al cliente, era el cliente el que tenia que realizar las peticiones
contra el servidor para recuperar la nueva información. Evidentemente,
el hecho de que el servidor pueda comunicarse con el cliente para
informar sobre cualquier “novedad”, es una característica que siempre ha
resultado muy útil y pensando en ello, se han diseñado varias
alternativas que funcionan sobre el protocolo HTTP, como por ejemplo
Comet, Push o con el uso de conexiones HTTP persistentes. Con estas
alternativas, el servidor puede enviar datos al cliente sin que el
cliente tenga que iniciar una interacción, son técnicas que se utilizan
actualmente en muchos servicios en Internet y en su día, GMail también
lo hacia, sin embargo, mantener una conexión HTTP durante un largo
periodo de tiempo es costoso y no es una muy buena idea en aplicaciones
que deben tener una baja latencia. Es aquí donde HTML5 viene “al
rescate” con los websockets.
Los websockets son una
alternativa muy óptima al problema descrito en líneas anteriores, ya que
permite crear conexiones TCP utilizando sockets entre el cliente
(navegador) y el servidor. Como con cualquier socket TCP, ambas partes
pueden transferir datos en cualquier momento y de esta forma el servidor
podrá enviar información al cliente cuando sea necesario.
Uso de websockets en HTML5
Para abrir una conexión entre cliente y servidor utilizando websockets, ahora se utilizan los esquemas “ws://” en lugar de “http://” y “wss://” en lugar de https://.
Además, el modelo de programación que debe utilizarse es basado en
eventos, dichos eventos alertarán al cliente cuando se realice una
conexión con el servidor, cuando ocurra un error, o cuando se reciba un
mensaje por parte del servidor. Para crear un websocket, se debe
ingresar la dirección del servidor web con el esquema “ws://” o “wss://”
y especificar alguno de los subprotocolos soportados en la
especificación de websockets (ver: http://www.iana.org/assignments/websocket/websocket.xml).
Después de abrir una
conexión con el servidor, el cliente puede enviar mensajes con la
función “send” que se encuentra definida en la referencia a la conexión
creada anteriormente. Los datos que se pueden enviar pueden ser cadenas
de texto o datos binarios representados con los objetos Blob o
ArrayBuffer. Un ejemplo del uso de websockets desde el lado del cliente
podría ser el siguiente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | connection.onopen = function () {
connection.send( 'Hello Server!' );
};
connection.onerror = function (error) {
console.log( 'WebSocket Error ' + error);
};
connection.onmessage = function (e) {
console.log( 'From Server: ' + e.data);
};
connection.send( 'Hello cutty server!' );
var img = canvas_context.getImageData(0, 0, 200, 200);
var binary = new Uint8Array(img.data.length);
for ( var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);
var file = document.querySelector( 'input[type="file"]' ).files[0];
connection.send(file);
|
En el ejemplo anterior se
ha creado una conexión con el servidor utilizando la clase WebSocket,
la cual se encuentra disponible en la mayoría de navegadores modernos.
La instancia creada de dicho objeto es utilizada para declarar una serie
de funciones de callback que serán invocadas cuando se produzcan
diferentes tipos de eventos posibles.
Por otro lado, aunque se
trata de una tecnología muy interesante y que se comienza a utilizar con
mayor frecuencia en aplicaciones web, no obstante la principal
dificultad a la hora de utilizar websockets, es la imposibilidad de
establecer conexiones utilizando servidores proxy por medio y dado que
en la mayoría de entornos empresariales, el uso de servidores proxy es
bastante común, nos encontramos con una limitación que hay que tener en
cuenta cuando se habla de utilizar WebSockets en aplicaciones web. La
razón de esto, es que los WebSockets utilizan el valor “upgrade” para la
cabecera “Connection” o directamente la cabecera “Upgrade” con el el
valor “WebSocket” y dicho valor indica que la conexión que inicialmente
se ha establecido utilizando HTTP, debe “actualizarse” para utilizar
sockets del tipo TCP. Este tipo de cambios en las conexiones no son
soportados por los servidores proxy del tipo HTTP, ya que están
diseñados para trabajar con paquetes de datos que utilizan el protocolo
HTTP, en este caso, la conexión es automáticamente cortada por el
servidor. Para “mitigar” este problema existen varias soluciones, como
por ejemplo el uso del proyecto Apache Camel (http://camel.apache.org) o mi favorita, el uso de “mod_proxy_wstunnel” (ver: http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) sobre esta extensión de Apache os hablaré en una próxima entrada.
Hasta este punto se ha
hablado del lado del cliente, sin embargo en el lado del servidor se
requiere un cambio de enfoque muy importante, ya que ahora hablamos de
que el servidor debe soportar múltiples conexiones abiertas al mismo
tiempo, con el consecuente consumo de recursos que aquello implica, por
éste y otros motivos, la mayoría de servidores web modernos soportan
modelos “Non-bloquing IO” o lo que es lo mismo, varios grupos de hilos
que se ejecutan de forma concurrente y asíncrona para el procesamiento
de respuestas. Se trata de un modelo de arquitectura de software
ampliamente aceptado y utilizado hoy en día, tanto en servidores como en
sistemas operativos. Alguno de los servidores web que soportan estas
características (sin ser una lista exhaustiva y basándome únicamente en
los que he probado) se mencionan a continuación.
Apache Web Server 2.2 / 2.4 (http://httpd.apache.org/)
Jetty (http://www.eclipse.org/jetty/)
Apache Tomcat (http://tomcat.apache.org/)
JBoss (http://www.jboss.org/)
Socket.IO (http://socket.io/)
Pywebsocket para Apache web Server. (http://code.google.com/p/pywebsocket/)
Tornado (https://github.com/tornadoweb/tornado)
Vulnerabilidades comunes en el uso de websockets
Ahora que está claro qué
son y para qué sirven los websockets, es el momento de hablar sobre las
vulnerabilidades que se pueden producir cuando se implementan de forma
indebida o con pocos controles sobre los datos intercambiados entre
cliente y servidor. Estamos hablando de una tecnología joven, que si
bien tiene un potencial enorme a la hora de crear aplicaciones web
robustas, esto no es gratis y tiene sus riesgos. Algunos de dichos
riesgos se explican a continuación.
Transporte no securizado y vulnerable a ataques MITM.
Como se ha mencionado
anteriormente, los websockets funcionan utilizando el protocolo TCP, lo
que habilita muchas posibilidades a la hora de realizar conexiones
contra multitud de servicios, sin embargo si el canal de comunicación no
se encuentra debidamente securizado, un ataque del tipo MITM puede
comprometer todos los mensajes enviados entre cliente y servidor.
Siempre es una buena practica utilizar el contexto “wss://” para
establecer conexiones cifradas con TLS.
Los websockets no soportan autenticación ni autorización.
El protocolo de
websockets no soporta los mecanismos tradicionales de autenticación y
autorización. Es un asunto relacionado con la implementación propiamente
dicha del protocolo y en el caso de que el cliente y el servidor
intercambien información sensible, además de securizar el canal de
comunicación utilizando TLS, también es recomendable utilizar mecanismos
de autenticación basados en tokens/tickets. Dichos mecanismos son
bastante populares en implementaciones REST, donde algunos servicios
solamente pueden ser consumidos si el cliente cuenta con un token de
autenticación valido y dicho token se vincula con el servidor por medio
de una cuenta de usuario. Dicho patrón de autenticación se ha vuelto
cada vez más popular y en aplicaciones que utilizan websockets que
requieren mecanismos de control sobre los usuarios autenticados, es una
excelente forma de mantener un control de acceso a recursos sensibles.
Validación en los datos de entrada
Aunque los websockets
utilicen TCP para realizar las conexiones entre clientes y servidores,
aun nos encontramos en el contexto de una aplicación web y es importante
validar los datos de entrada de los usuarios. En el caso de no validar
los campos de entrada adecuadamente, se pueden producir vulnerabilidades
del tipo XSS aunque para la comunicación se ha un protocolo distinto a
HTTP.
Vulnerabilidades Cross Site Request Foregy
Tal como se comentaba en
un articulo anterior, las políticas de “same origin policy” que aplican
cuando se trata de compartir recursos entre distintos dominios, ahora ya
no son tan estrictas cuando se utiliza la cabecera HTTP “Origin”. Tal
como se mencionaba en dicho articulo, se trata de una característica que
está muy bien cuando se trata de compartir recursos con dominios
“fiables” y cuando hablamos de relaciones de confianza en el mundo de la
seguridad de la información, siempre pueden haber situaciones que le
permiten a un atacante abusar de dichas condiciones.
Los websockets no están limitados a las restricciones de SOP,
esto significa que un atacante puede iniciar una petición websocket
desde su sitio web malicioso contra un endpoint (ws:// o wss://) de una
aplicación web aunque no se encuentre en el mismo dominio. Esto tiene
unas implicaciones tremendas, ya que el handshake que ejecutará el
cliente para iniciar una petición WebSocket es una petición HTTP regular
y los navegadores enviarán las cookies y cabeceras HTTP de
autenticación aunque se trate de un dominio cruzado, si y sólo si, el
servidor web no valida adecuadamente la cabecera “Origin”.
Para que el lector se haga una idea del problema, supongamos que la
víctima ha iniciado sesión en un sitio web que tiene uno o varios
endpoints “ws” o “wss” y en una pestaña nueva del navegador, ingresa en
una página web maliciosa controlada por el atacante. Dicha página podría
realizar una petición WebSocket contra el sitio web en el que el
usuario se encuentra identificado y si dicho sitio web, no valida
adecuadamente el valor de la cabecera “Origin”, la respuesta a la
petición del atacante podrá contener, entre otras, las cookies y
cabeceras de autenticación utilizadas por el usuario, sin importar que
se trate de un dominio cruzado. Esta situación puede dar lugar a una
vulnerabilidad del tipo CSRF.
Fuente