21 junio 2011

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!

8 comments :

Rubén Díaz dijo...

Muy buena entrada. Son cosas básicas a tener en cuenta pero que las veces que tenemos que dedicarnos a ello se nos pasan.

Un repasito por IPTables nunca viene mal: http://www.securitybydefault.com/search?q=iptables

Alberto Ballano dijo...

Buen post! Lo cierto es que a veces es útil recordar estas cosillas que, aunque puedan parecer triviales, son las que pueden cargarse un sistema bastante más grande si nos olvidamos de ellas =)

A mí personalmente me encanta programar servidores TCP con sockets y tendré muy en cuenta estas cosillas para la próxima vez, gracias.

Un saludo!

Fer-nand dijo...

Muy bueno, felicitaciones. 

Me gustaría recopilar buena documentación acerca de Sockets en C/C++ e incluso C# para .Net ya que estoy barajando en que lenguaje desarrollar un proyecto. Si me podéis sugerir lecturas os lo agradecería.
Gracias

Alberto Ortega dijo...

¡Gracias a todos por los comentarios!

En la creación de nuevas conexiones, otra solución que se puede aplicar es permitir un número máximo de conexiones, y cuando se supere ese número máximo no permitir más hasta que se liberen otras.

También es facil de implementar, y podemos controlar cuantas conexiones máximas podrá tener nuestro servicio :)

Un saludo!

LeGNa LeGNa dijo...

Buen post! 
Este año implementé aplicación con sockets para controlar las sesiones de los TPV q se conectaban en una tienda.
Uno de los problemas era ese, hilos que se quedaban colgados. La forma de solucionarlo fué utilizando variables de sesión, por lo que terminarían caducando... me gusta más esta idea que has propuesto.

Borja Ferrer dijo...

Legna
No se en que vas a programarlo, pero si por casualidad lo haces en entornos Vistual Studio (C#,J#,.NET) El objeto socket ya tiene una gestion de eventos para cuando un canal se cae/desconecta/corta... etc por lo que es sencillisimo capturar cuando un canal se cierra.
En C/C++ se que el objeto Socket tb es muy completo en ese aspecto, no creo que levante eventos de forma automatica, pero si que puedes dejar un Listener escuchando en un Thread independiente y él mismo en caso de que el canal se cierre o corte te avisará a la salida, solo debes poner la comprovacion de PQ el listener se activa, mirar el estaod de la conex, o los datos recibidos (data_recived == 0?), etc...
Espero que te sea de ayuda.

Q.

Ro_menchaca dijo...

Voy a regalar a hija mi celular, para tener un nuevo. Uso whatsApp, y tengo a una amiga registrada quitar los vinculos, y no dejar rastro.

ElAngelGris dijo...

Hola acabo de leer el post y mirar el video... despues lei abajo que esto ya no funciona... Mi pregunta es;: Algun metodo que funcione actualmente? esta muy bueno el post, muy bien explicado