Transparencias de clase sobre «Firewalls»

Continuando con las publicaciones de viejas transparencias de clase de Administración e Instalación de Redes de Computadores, en particular, de las que dedicaba a seguridad en red, hoy toca el turno de la presentación que hice sobre el diseño y configuración de cortafuegos. Para estas transparencias usé como principal fuente de información el libro «Building Internet Firewalls» de D. Bremt Chapman y Elizabeth D. Zwicky, editado por O’Reilly.

¡Espero que os sea útil!


Reutilización del mismo puerto en el lado del cliente

La entrada anterior está escrita desde el punto de vista de los servidores, aunque en el lado del cliente, también podemos reutilizar puertos. ¿Cómo? Pues añadiendo, antes de la función connect de, por ejemplo, este código de cliente, las siguientes órdenes:

sock.setsockopt(socket.SOL_SOCKET, SO_REUSEPORT, 1)
sock.bind((«0.0.0.0»,3000))

la primera orden es la misma que hemos comentado ya y es la que nos activa el flag que permite reutilizar el puerto; la segunda es necesaria para «forzar» el puerto y que no sea uno aleatorio. Si lanzamos 2 conexiones, nos queda:

Screenshot from 2015-03-23 10:59:20

¡Espero que os sea útil!


Reutilización de puertos en GNU/Linux

Para todos los sistemas operativos, una conexión (comunicación) entre dos equipos está definida por cualquier combinación de la tupla formada por la dirección IP origen y destino, el puerto de origen y destino y el protocolo de transporte (TCP o UDP).  Así, en cualquier nodo de nuestra red, podremos utilizar la función bind para «anclar» nuestro servidor a cualquier puerto libre. Si el puerto ya está en uso, y si nuestro nodo dispone de más de una dirección IP, podremos unir nuestro proceso utilizando otra IP que no tenga «ocupado» el puerto que nos interesa. Si el puerto y la dirección están en uso, siempre podremos usar el otro protocolo de transporte, suponiendo, nuevamente, que esté libre (y nos interese, claro).

En definitiva, esta tupla (recordemos: ip origen, puerto origen, ip destino, puerto destino, protocolo de transporte) le permiten al sistema operativo identificar unívocamente, el proceso que debe encargarse de los datos de aplicación.

Así que, ¿a qué viene el título de la entrada? ¿Qué es reutilizar puertos?

Pues para responder, debemos ver las opciones SO_REUSEADDR y, sobre todo, SO_REUSEPORT cuyo comportamiento es distinto en función del sistema operativo donde las utilicemos (ver [1]).

La opción SO_USEADDR ya la mencioné en esta entrada en la que comentaba los timeouts de TCP; en ella indicaba que, en general, interviene en el tiempo de espera…

antes de poner disponible, de nuevo, un puerto tras su cierre y el objetivo es dejar tiempo suficiente para que los segmentos de esta conexión saliente desaparezcan del sistema

aunque hay más y es lo que voy a tratar en el resto de la entrada junto con la opción SO_REUSEPORT. Ambas son importantes para la programación de Sistemas Distribuidos; y podemos usarlas cuando nos convenga, pero sin olvidarnos que debemos disponer de una versión del núcleo igual o superior a la 3.9.

SO_REUSEADDR, en GNU/Linux (así como en BSD y otros sistemas operativos) introduce, tal y como podemos comprobar en la página man de la función socket [2], la posibilidad de reutilizar la misma combinación IP-Puerto en un servidor, siempre y cuando el puerto no esté en escucha activa (es decir, se haya ejecutado la función listen para ese puerto). Básicamente, nos permite reutilizar puertos UDP, teniendo un comportamiento diferente en otros sistemas como por ejemplo BSD (ver [1]).

SO_REUSEPORT, en GNU/Linux, permite asociar un número arbitrario de sockets a la misma pareja IP-puerto, tanto para TCP como para UDP. Esto solo tiene una limitación (además de que el flag debe estar activo, antes de ejecutar bind, en todos los procesos): todos los sockets deben de pertenecer a procesos con el mismo EUID (identificador de usuario efectivo) para evitar el secuestro de puertos (port hijacking).

¿Qué ventaja aporta? Si consultamos la página man de socket:

this option allows accept load distribution in a multi-threaded 
server to be improved by using a distinct listener socket for each 
thread.  This provides improved load distribution as compared to 
traditional techniques such using a single accepting thread 
that distributes connections, or having multiple threads that 
compete to accept from the same socket.
UDP sockets, the use of this option can provide better distribution
of incoming datagrams to multiple processes (or threads) las compared
to the traditional technique of having multiple processes compete 
to receive datagrams on the same socket.

¿Cómo lo implementamos? Veamos un ejemplo. Si tomamos el código del servidor sencillo descrito en esta entrada y le añadimos la siguiente orden, justo antes de invocar la función bind*:

s.setsockopt(socket.SOL_SOCKET, SO_REUSEPORT, 1)

tras varias ejecuciones del programa, tendremos el resultado de la imagen 1 :

Screenshot from 2015-03-20 10:53:54

Y ¿qué proceso recibe los datos de aplicación? ¿Todos? Pues no. Tanto en TCP como en UDP, el núcleo de GNU/Linux trata de repartir equitativamente el trabajo entre los procesos: las conexiones entrantes para TCP (las reparte equitativamente entre los procesos que están con la ejecución de la función accept en ese puerto) y los datagramas para UDP. En el caso específico de multicast, SO_REUSEADDR tiene el mismo comportamiento que SO_REUSEPORT en conexiones unicast.

Para el ejemplo visto, tras 2 conexiones (una desde el propio equipo y otra desde otro nodo de la red) tenemos que estas se asignan a 2 procesos diferentes (ver imagen 2)

Screenshot from 2015-03-20 10:56:55

¡Espero que os sea útil!

*Solo nos falta definir: SO_REUSEPORT=15

Referencias

1.- Muy completa la respuesta dada en: http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t

2.- man socket

3.- man setsockopt


Transparencias «viejunas» sobre IDS

Hace muchos años (en el 2006) impartí unas clases en la Universidad de Almería sobre sistemas de detección de intrusos en el curso anunciado aquí.

Las transparencias que hice las he subido a Slideshare y he hecho un collage para presentarlas en este blog como anuncié hace unos días. Aquí lo tenéis.

Sobre SNORT, teoría:

Y prácticas:

Sobre Honeyposts, teoría:

Y práctica:

Y, por último, sobre Tripwire:

¡¡¡Espero que os sean útiles a pesar del tiempo que tienen!!!


Transparencias de clase sobre DHCP

Tal y como comentaba ayer en esta entrada, voy a ir dejando todas mis transparencias de clases, ponencias, conferencias y cursos en Slideshare.

Hoy toca el turno del servicio DHCP, clase que impartía en la asignatura de Administración e Instalación de Redes de Computadores en las titulaciones de Informática.

De las diferentes versiones (en función de los años) que tengo, he decidido publicar estas:


Protección contra ARP poisoning

Ya he escrito en varias ocasiones acerca de problemas que permiten insertar un equipo entre nosotros y nuestros objetivos (Ataque Man In The Middle): aquí y aquí. En esta entrada, que amplía el «primer aquí», el objetivo es explicar en qué consiste y cómo podemos abordarlo.

¿Qué es ARP Poisoning?

Consiste en conseguir indicarle a un nodo que la MAC asociada a una IP (por ejemplo la de nuestro router por defecto) es la otra diferente a la que tiene realmente el equipo objetivo. Conseguirlo es bastante fácil y, además, simplemente hay que usar el propio protocolo ARP. Una imagen vale más que mil palabras y, creo, la de la Wikipedia refleja bastante bien en qué consiste el envenamiento de ARP (también llamado, por el efecto, ARP Spoofing):

Leer más


IGMP: tráfico multicast y filtros

Cuando diseñamos una red una de las tecnologías/infraestructuras que planificaremos será la instalación de un cortafuegos*. En estas circunstancias tenemos que tener claro qué paquetes dejamos pasar, cuales desechamos, y todo en función de nuestra política de seguridad y sin perder la funcionalidad para la que estamos diseñando nuestra red.

Si utilizamos OSPF o, en general, si queremos manejar tráfico multicast, debemos habilitar los paquetes IGMP en nuestros filtros (suponiendo que tenemos la estrategia de denegar todo lo que no está explicitamente habilitado) ya que es el protocolo que utilizaran hosts y routers para establecer los miembros de los grupos de multicast. De este protocolo, debemos tener en cuenta, sobre todo, que se encapsula en datagramas IP con el número 2 de protocolo (esto implica que NO utiliza UDP ni TCP ni, por tanto, tiene puertos). Otro detalle que debemos tener en cuenta es que el TTL de los paquetes de IGMP, por defecto, es 1, lo que implica que no «atravesarán» los routers y no saldrán de la subred en la que se genera, aunque puede aumentarse para localizar servidores lejanos utilizando este campo como «barrera» y configurando los routers para que sean transparentes y no decrementen el TTL**.

La ejecución de las siguientes órdenes provocará que nuestro router de filtrado acepte y saque mensajes IGMP:

iptables -A INPUT -p igmp -j ACCEPT
iptables -A OUTPUT -p igmp -j ACCEPT

Para el caso de los grupos multicast relacionados con OSPF que comentábamos en la entrada anterior, podríamos limitar las reglas habilitando solo los grupos relacionados con OSPF:

iptables -A INPUT -p igmp -d 224.0.0.5 -j ACCEPT
iptables -A INPUT -p igmp -d 224.0.0.6 -j ACCEPT
iptables -A OUTPUT -p igmp -d 224.0.0.5 -j ACCEPT

Referencias

  1. Building Internet Firewalls, de Elizabeth D. Zwicky, Simon Cooper y D. Brent Chapman
  2. https://tools.ietf.org/html/rfc3376, RFC para IGMP versión 3
  3. http://tools.ietf.org/html/rfc2236, RFC para IGMP versión 2

* Prefiero usar el concepto de cortafuegos para la infraestructura de protección de red que diseñamos y que puede estar compuesta por varios elementos donde, uno de ellos, el dispositivo de red que filtra paquetes y que -casi- todo el mundo denomina cortafuegos, yo prefiero denominarlo router de filtrado.

** Una orden genérica para este caso es: iptables -A FORWARD -p igmp -j ACCEPT Si queremos, por ejemplo, especificar que el tráfico que queremos es el relacionado con el audio/vídeo: iptables -A FORWARD -p udp -m udp -d 239.0.0.0/16 –dport 5001 -j ACCEPT

 


Como cambiar la MAC en GNU/Linux

Existen ciertas circunstancias en las que cambiar la MAC de nuestra tarjeta de red nos facilita el trabajo. Por ejemplo, si tenemos filtros por MAC (como ocurre con muchos routers ADSL) y cambiamos la tarjeta de red de nuestro equipo, quizás sea más fácil cambiar la MAC de la nueva por la que tenía la vieja y que se le apliquen todos «las ventajas» que tengamos implementadas, que ir cambiando los filtros en cada uno de los dispositivos.

Sea por la razón que sea (alguna también puede no ser «benigna»), en Debian se realiza con la ejecución de estos comandos (suponemos que la NIC es eth0):

ip link set dev eth0 down
ip link set dev eth0 address aa:bb:cc:dd:ee:ff
ip link set dev etho up

Si queremos comprobar que todo ha ido bien: ip link show eth0

Para que se quede fijo y al reiniciar dispongamos de la nueva MAC, podemos crear un guión con las 3 órdenes anteriores y lanzarlo en el arranque de la máquina.


El cliente en Python

Y para terminar con el ejemplo de un esqueleto cliente-servidor en Python, el cliente:

#!/usr/bin/python
#encoding:utf-8
try:
 import socket
 import optparse,sys
except:
 print("Error running 'import optparse,socket,sys'. Maybe you have to install some python library :)")
parser = optparse.OptionParser("usage%prog " + "-s <target server> -p <target port>")
parser.add_option('-s', dest = 'server', type = 'string', help = 'Please, specify the target server '-s server'')
parser.add_option('-p', dest = 'port', type = 'string', help = 'Please, specify the target port '-p port'')
(options, args) = parser.parse_args()
if (options.server == None):
 print '[-] You must specify a target server: -s server.'
 exit(0)
if (options.port == None):
 print '[-] You must specify a target port: -p port'
 exit(0)
server = options.server
port = int(options.port)

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect the socket to the port on the server given by the caller
server_address = (server, port)
print('connecting to %s port %s' % server_address)
try:
 sock.connect(server_address)
except socket.error , msg:
 print 'Connect failed. Error code: ' + str(msg[0]) + 'Error message: ' + msg[1]
 sys.exit()
try:
 message="Speaking with server "
 print("sending %s" % message)
 sock.sendall(message)
 amount_received = 0
 amount_expected = len(message)
 while amount_received < amount_expected:
 data = sock.recv(16)
 amount_received += len(data)
 print("received %s" % data)
finally:
 sock.close()

Creo que el código se explica solo. Tras realizar una correcta conexión, el cliente intercambiará un mensaje con el servidor. En ese trozo es donde se debería implementar el protocolo de aplicación que necesitemos.

La ejecución del cliente en el servidor implementado en las imágenes

Captura de pantalla de 2014-02-14 18:42:56

Referencias:

  1. Python para todos, de Raúl González Duque
  2. Entrada con la versión sencilla del servidor
  3. Entrada con la versión concurrente del servidor

SD: Esqueleto de servidor concurrente con Python (y2)

Continuando con programas en Python (que para eso es el lenguaje de programación de moda ), veremos un servidor que acepta múltiples peticiones de clientes, cada una de ellas, atendidas por un servicial hijo. Esta constituye la principal mejora que debíamos añadir  al código de servidor básico que vimos en la entrada referenciada.

Aunque sea solo sea un esqueleto de un proceso servidor, para que cumpla un mínimo, debe ser concurrente para que admita más de un cliente. Pensando en estos términos, en seguida nos puede venir a la memoria los Threads, pero con estos Python nos está “engañando” de forma vil. Si consultamos la información sobre implementación y threads en [1], nos daremos cuenta que, al usarlos, lo que Python estará haciendo es que en único proceso irá intercambiando la ejecución de los “hilos” que ha creado para dar la sensación que todo se está ejecutando en paralelo, pero no lo es.

Por esto, para que sea concurrente, con Python he usado procesos y no hilos (aunque podréis encontrar versiones con hilos como esta, esta y esta)

El código:

#!/usr/bin/python
#encoding:utf-8
try:
    import socket,sys,optparse
except:
    print("Error running 'import socket,sys,optparse'. Maybe you have to install some python library")
try:
    import sys,os
except:
    print("Error running 'import  sys,os'. Maybe you have to install some python library")

def comunication(connection, addr):
    print 'Connected with ' + addr[0] + ':' + str(addr[1])
    while True:
        #receive data
        try:
            data = connection.recv(1024)
            #process data
            if not data:
                break
            #elif re.match(data, "QUITn."):
            elif data == "QUITn":
                print 'Received data: ' + data + " from " + addr[0] + ':' + str(addr[1])
                reply = 'BYE'
                connection.send(reply) #send reply
                break
            else:
                print 'Received data: ' + data + " from " + addr[0] + ':' + str(addr[1])
                reply = 'OK...' + data
                connection.send(reply) #send reply
        except KeyboardInterrupt:
            print
            print "Stopped server."
            break
    connection.shutdown(socket.SHUT_RDWR)    
    return

def main():
    parser = optparse.OptionParser("usage%prog " + "-d <ip> -p <target port>")
    parser.add_option('-d', dest = 'ip', type = 'string', help = 'Please, specify the target server')
    parser.add_option('-p', dest = 'port', type = 'string', help = 'Please, specify the target port')
    parser.add_option('-q', dest = 'queue', type = 'string', help = 'Please, specify the queue size')
    parser.add_option('-P', dest = 'process', type = 'string', help = 'Please, specify the maximun number of process')
    (options, args) = parser.parse_args()
    if (options.ip == None):
        print '[-] You must specify a ip direction to listen to.'
        exit(0)
    if (options.port == None):
        print '[-] You must specify a port.'
        exit(0)
    HOST=options.ip
    PORT=int(options.port)
    if (options.queue == None):
        QUEUE=1
    else:
        QUEUE=int(options.queue)
    if (options.process == None):
        PROCESS=2
    else:
        PROCESS=int(options.process)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # Create a socket object
    print 'Socket created'
    try:
        s.bind((HOST, PORT)) # Bind to the port
    except socket.error , msg:
        print 'Bind failed. Error code: ' + str(msg[0]) + 'Error message: ' + msg[1]
        sys.exit()
    print 'Socket bind complete'
    s.listen(QUEUE) # Now wait for client connection
    print 'Socket now listening'
    while True:
        try:
            conn, addr = s.accept()
        except KeyboardInterrupt:
            print
            print "Stopped server."
            s.close()
            break
        except socket.error, msg:
            print "Socket error! %s" % msg
            s.close()
            break
        try:
            pid=os.fork()
            if (pid==0): #Child
                comunication(conn, addr)
                conn.close()
                s.close()
                print("Bye child")
                exit(0)
            else: #Father
                print("Made child %s..." % pid)
        except OSError, e:
            sys.stderr.write("Error making process child (fork)")
            break
if __name__ == "__main__":
    main()

Aspectos que debemos tener en cuenta de este esqueleto:

  • En la función comunication(…) es donde pondremos el manejo de la comunicación entre cliente y servidor: es decir, se implementará el protocolo de aplicación que permitirá la conversación entre cliente (para solicitar un recurso/know how) y el servidor.
  • Debemos finalizar educadamente (shutdown)
  • El código que ejecutará el hijo es el comprendido dentro del ‘if PID==0′

En la siguiente imagen se puede comprobar que al recibir 4 peticiones de servicio, el número de procesos serán 5: 4 hijos, uno por cada conexión (y que se encargan de gestionarla) y el padre que está a la espera de más peticiones. Podemos limitar el número máximo de procesos que creamos, si lo consideramos conveniente (se programa y punto 😉 )

Captura de pantalla de 2014-02-03 17:27:42Referencias:

  1. Python para todos, de Raúl González Duque
  2. Entrada con la versión sencilla del servidor

Las cookies nos permiten ofrecer nuestros servicios. Al utilizar nuestros servicios, aceptas el uso que hacemos de las cookies. Más información.