domingo, 10 de julio de 2011

Seguridad en sockets TCP

Seguridad en sockets TCP: "
En alguna ocasión, bien porque necesitamos desarrollar una solución cliente / servidor, o bien porque queremos implementar otro servicio conocido, tenemos que programar un servidor TCP. Hay una serie de buenas prácticas que nos pueden ayudar a conseguir una mayor estabilidad frente a ataques o saturación del servicio.


Si participasteis en el primer wargame de SbD que organizamos hace unos meses, recordareis la primera prueba de networking que se trataba de un servidor que emulaba ser la configuración de un router. Estas buenas prácticas de las que hablamos fueron implementadas y, puede que gracias a ello, el servidor estuvo activo constantemente durante todo el concurso.


Está programado en ruby, y vamos a tomarlo como ejemplo. Hay que tener en cuenta que, al ser ruby un lenguaje de alto nivel, es posible que no necesitemos solucionar problemas que pueden darse en lenguajes de otro tipo, o de bajo nivel. Dicho ésto, ¡empezamos!


La estructura del programa es algo parecido a esto:


login

if correcto

bucle

menu

leer_opción

realizar_acción

fin

fin


Como de casualidad el servidor está a la escucha y cuando recibe una nueva conexión crea un hilo nuevo para servir a ese cliente y volver a ponerse a la escucha.


server = TCPServer.new('', srvport)
loop do
socket = server.accept
if socket
newclient(socket)
end
end


En este caso la función newclient() toma el socket y crea el hilo.


Uno de los problemas que podemos encontrar en este punto es que si se reciben muchas conexiones de forma simultánea, se crearán más hilos de los que el lenguaje o el sistema puede soportar, por lo que el programa morirá.


Ésto depende mucho del lenguaje de programación y de la versión. Por ejemplo, en versiones antiguas de ruby sucedía, pero en las versiones más modernas parecen haberlo controlado de alguna forma.


Las soluciones que podemos tomar son variadas. Personalmente la que más me convence es poner un pequeño tiempo de espera entre que se acepta la conexión y se crea el hilo. Nuestro código quedaría de la siguiente forma:


server = TCPServer.new('', srvport)
loop do
socket = server.accept
sleep(0.05)
if socket
newclient(socket)
end
end


Aunque controlemos el número de conexiones al incio, puede que alguien con malas intenciones realice conexiones y luego las deje sin actividad. Ésto podría causar que se llegara al límite de conexiones que soporta el sistema, haciendo que quede saturado.


Para solventar este problema podemos usar timeouts por inactividad, de forma que si un usuario no muestra actividad durante X tiempo, se cierre el socket y el hilo muera. Para ello, hice una función alternativa para leer del socket.


def read_socket(s, thread, tout = 15, size = 500)
begin
Timeout::timeout(tout) do
info = s.recv(size)
return info
end
rescue Timeout::Error
s.close
thread.kill
return false
end
end


La función recibe el socket y el hilo como parámetros, también podemos pasar el tiempo de timeout y el tamaño de lectura del socket. Si se supera el timeout se cierra el socket y se mata el hilo.


En nuesto caso particular hay otra cosa más con la que hay que contar. ¿Y si se crean conexiones de bots que entran en el bucle y realizan acciones para que no se les expulse por timeout?


Teniendo en cuenta esa posibilidad, la solución pasa por limitar el número de interacciones en el menú. En este caso estaba limitada a 50.



Tomando estas precauciones podemos conseguir un servicio bastante estable y resistente a ataques o saturación. ¿Conoces o sigues más medidas? ¡Compártelas en los comentarios!
"