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

 


Filtros de red para OSPF (Open Shortest Path First)

Si en nuestros routers utilizamos la estrategia de denegar todo por defecto y permitir expresamente lo que deseamos, en el caso de que estos equipos tengan configurado que aprendan sus rutas con OSPF, debemos habilitar dicho protocolo. Para ello, debemos tener en cuenta que OSPF

  • se encapsula en datagramas IP con el número 89 de protocolo (esto implica que NO utiliza UDP ni TCP ni, por tanto, tiene puertos),
  • usa tanto paquetes multicast (los grupos 224.0.0.5 y 224.0.0.6 y lo que nos obliga a configurar, usar y habilitar en nuestros filtros IGMP*) como unicast,
  • el TTL de sus paquetes es 1, por lo que no atravesarán un cortafuegos, salvo que configuremos un túnel (http://www.cisco.com/en/US/tech/tk583/tk372/technologies_configuration_example09186a00800a43f6.shtml),
  • y por último, dispone de un identificador de tipo de paquete (nos permite mejorar las reglas, aunque dependemos del software de filtrado).

Con iptables, la forma más fácil es ejecutar las siguientes ordenes**:

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

Si queremos ser más “finos”, debemos indicar las IPs de origen, destino y, si lo permite el software de filtrado, el tipo de paquete de OSPF. Las reglas en este caso***:

#hello ospf between rt_ext and rt_int
iptables -A INPUT -s rt_ext -d 224.0.0.5 -p 89 -j ACCEPT
iptables -A OUTPUT -s rt_int -d 224.0.0.5 -p 89 -j ACCEPT
#database description, link state request, asking for information about a particular link and request/update link state
iptables -A INPUT -s rt_ext -d rt_int -p 89 -j ACCEPT
iptables -A OUTPUT -d rt_int -s rt_int -p 89 -j ACCEPT
#link state update, flooding all link states from rt_ext router
iptables -A INPUT -s rt_ext -d 224.0.0.5 -p 89 -j ACCEPT
iptables -A OUTPUT -s rt_int -d 224.0.0.6 -p 89 -j ACCEPT
iptables -A INPUT -s rt_ext -d 224.0.0.6 -p 89 -j ACCEPT
iptables -A OUTPUT -s rt_int -d 224.0.0.5 -p 89 -j ACCEPT
#rt_int router link state update from rt_ext router
iptables -A OUTPUT -s rt_int -d 224.0.0.5 -p 89 -j ACCEPT
iptables -A INPUT -s rt_ext -d 224.0.0.6 -p 89 -j ACCEPT
iptables -A OUTPUT -s rt_int -d 224.0.0.6 -p 89 -j ACCEPT
iptables -A INPUT -s rt_ext -d 224.0.0.5 -p 89 -j ACCEPT

Referencias

  1. Building Internet Firewalls, de Elizabeth D. Zwicky, Simon Cooper y D. Brent Chapman

* Es muy sencillo, pero lo dejo para otra entrada 😉

**Estamos considerando que NO queremos reenviar (forward). Si quisiéramos hacerlo, ver el punto 3 de las consideraciones de OSPF que he escrito en la entrada. Además, estas reglas deben ejecutarse en los routers designados.

***Las reglas se deben de ejecutar en los router afectados (en rt_ext cuando ‘-s rt_ext’ o en rt_int si ‘-s rt_int’), salvo que dispongamos de un cortafuegos específico y, en ese caso, se ejecutan todas en él. Recordad que debe disponer de un túnel (ver **)


Timeouts de TCP en GNU/Linux y opciones para “afinar” un servidor

Los tiempos de espera de TCP vistos en la entrada anterior se pueden consultar y modificar en GNU/Linux a través de los ficheros apropiados del /proc y, para afinar un servidor y mejorar sus prestaciones en base a estos timeout, podremos modificar sus valores en este sistema de archivos sin necesidad de recompilar el núcleo. Pero, antes de nada, si lo que queremos es afinar nuestro sistema, lo que debemos es de plantearnos es esta pregunta: ¿cuántos puertos concurrentes podemos tener abiertos? Para ello:

Leer más


Timeouts de TCP

En TCP(1) existen 4 tipos de timeouts (tiempos de espera) y cada uno de ellos se aplica en diferentes momentos y con diferente duración. Algunos, incluso, podemos evitarlos si es lo que deseamos…

Conocer estos tiempos es importante tanto para administradores de sistemas como para programadores de aplicaciones distribuidas (¡¡¡pocas no lo son ya!!!)

Leer más


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

Rogue/Spoofing DHCP

Un problema de seguridad que podemos tener es que “alguien” logre situar, entre nosotros y nuestro destinatario, un elemento intermedio que haga lo que le dé la gana: desde registrar lo que hacemos (y nuestras contraseñas, por supuesto, como la NSA pero más modesto 🙂 ) hasta reenviar nuestras peticiones a otros servicios similares al que necesitamos/queremos, pero no lo “reales”.

Leer más


Lista de puertos usados por un proceso…

Dado el PID  y sin necesidad de netstat o herramientas similares, podéis saber qué puertos tiene en uso (y su estado y mucha más información) dicho proceso con algo parecido a esto:

ls -la /proc/$proceso/fd | grep socket | cut -d’>’ -f2|cut -d’:’ -f2 | while read id; do grep $id /proc/net/tcp; done;

La información resultante la podéis interpretar con la documentación del Kernel de GNU/Linux (proc_net_tcp.txt), si no os aclara la primera línea del fichero /proc/net/tcp:

sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops

Para el caso que me ocupaba, sobraba con saber que de la línea:

4: 00000000:445C 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 71298 1 f61904c0 300 0 0 2 -1

el primer campo (4) es un identificador de entrada; el segundo (00000000:445C) son la IP de origen y el puerto de origen; el tercero es la IP y puerto de destino y el cuarto (0A) es el estado (0A= Listen)

Si alguien no se fía de los resultados que le muestran sus herramientas, recordad que siempre está /proc


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