Información blog

Linux, tutoriales, noticias, sistemas, redes y seguridad informática, entre otras cosas.

Mostrando entradas con la etiqueta Firewall. Mostrar todas las entradas
Mostrando entradas con la etiqueta Firewall. Mostrar todas las entradas

lunes, 7 de octubre de 2019

Auditar nuestra centralita Asterisk con SIPVicious

Siempre que se piensa en el concepto de la seguridad, se parte del concepto: Mínima superficie de exposición, mínimo privilegio posible. Concepto que si bien es importante, no implica que un servicio sea inexpugnable, pues si bien siempre se intenta que un servicio no sea accesible desde lugares indeseados, hay ciertos servicios tales como páginas web o centralitas de Asterisk, que tienen que tener cierta flexibilidad. Esa flexibilidad nos ofrece ciertas ventajas, pero al mismo tiempo pueden ser posibles objetivos de ataques no deseados... Ataques que siempre conviene tener en cuenta.

sipvicious

Es por ello que hay algunas herramientas que nos pueden ayudar a realizar auditorías sobre nuestras centralitas de Asterisk, y que pueden servirnos para "poner a prueba" nuestro sistema; ya que nos ayuda a prevenirnos de ataques reales que pueden ser muy dañinos. Supongamos que alguien se hace con el control de una extensión telefónica capaz de hacer llamadas internacionales; en muy poco tiempo el coste económico de la medida sería brutal. Es por ello que mediante estas herramientas podemos hacernos una idea de las fortalezas y debilidades de nuestra PBX. Hay múltiples herramientas, pero la más popular sería SIPVicious.

SIP Vicious es una herramienta de software libre muy extendida que incluso está incluida hoy en día dentro de la archi-conocida distribución Kali. Para poder utilizarla, esta utilidad usa por defecto el puerto 5060 para escuchar los mensajes de respuesta, a menos que esté en uso por nuestro propio Asterisk u otro servicio, en cuyo caso usaría el siguiente puerto que esté libre (5061,5062...); con lo que es importante tener esos puertos abiertos en el equipo atacante si queremos poder recibir los mensajes de respuesta.

La utilización es muy sencilla, y solamente requiere tener python instalado (el cual ya viene pre-instalado la gran mayoría de las veces) y si caso la herramienta git, si bien no es necesario al 100%, nunca viene mal tenerla instalada. En caso de no utilizar git, sí que sería necesario descargarnos la herramienta zip.

Para descargar la aplicación, siempre y cuando no se use Kali que ya la incluye por defecto, la cual se encuentra Github, se puede hacer desde la consola de dos formas; o bajando el fichero comprimido o vía git:

Vía git:
git clone https://github.com/EnableSecurity/sipvicious.git

Vía directa:
wget https://github.com/EnableSecurity/sipvicious/archive/master.zip
unzip master.zip

Cualquiera de estos procedimientos dará como resultado una carpeta que puede llamarse sipvicious en caso de usar git, o sipvicious-master, pero independientemente del nombre ambos tienen el mismo contenido, dentro del cual el que nos interesa es el que se encuentra dentro de la subcarpeta sipvicous. Dicha carpeta tiene diferentes programas escritos en python, dentro de los cuales, 3 serían los que principalmente nos interesarían: svmap.py svwar.py y svcrack.py.

La primera herramienta, svmap.py, sería la que usaríamos para escanear un rango de IPs a nuestra elección, junto con sus respectivos puertos con el fin de ver si se encuentra alguna que dé como respuesta alguna PBX, como por ejemplo Asterisk. Dicho escaneo usaría la sintaxis:

svmap.py -p puerto_inicio-puerto_fin IP_inicio-IP_fin -m opciones

El puerto sería opcional, y se podría poner un puerto únicamente si se desea; en caso de no usar ninguno, se usaría el puerto standard SIP, 5060. Con respecto a la IP que se desea escanear, se puede poner  una sola o un rango de IPs, dependiendo de nuestras necesidades. En caso de querer testear solamente nuestro equipo, solamente con una IP sería suficiente...

En el apartado opciones, es opcional usarlo, pero una opción muy común sería la de INVITE, ya que es mensaje que se envía a una PBX cuando un usuario se desea registrar. Un comando de ejemplo podría ser:

./svmap.py -p 5060 192.168.1.102 -m INVITE

Esto nos puede dar como resultado o bien un resultado nulo, que sería lo deseable, o bien que nos dé información de la PBX, incluyendo la versión que usa el equipo contrario. Por ejemplo, usando dicho comando contra mi Asterisk, me ha dado como resultado:

svmap_ejemplo

En caso de que se muestre este mensaje en nuestra centralita, lo ideal sería intentar ocultarlo lo antes posible, pues esto nos brinda dos datos a nuestro atacante. El primero es que el puerto 5060 tiene un Asterisk asociado, lo cual es susceptible a ataques, pero no solo eso, sino que además nos brinda la versión de éste, que puede ser más peligroso aún, ya que si uno conoce la versión de software que uno usa, puede buscar vulnerabilidades asociadas a dicha versión, que puede hacer innecesario el utilizar ataques de fuerza bruta contra la centralita.

Supongamos que hemos recibido el mensaje anterior, lo que pasaríamos a hacer ahora sería a intentar ver qué extensiones están "disponibles" en nuestro objetivo. Esto lo lograríamos mediante la segunda utilidad: svwar.py

La utilidad svwar, es una utilidad muy simple que se dedica a mandar peticiones de registro contra la centralita objetivo. Si la PBX devuelve un mensaje "No matching peer found" o no respondiese, consideraría que no hay una extensión asociada, pero en caso de que considerase que la contraseña introducida para el registro, no es correcta, interpretaría que SÍ que hay una extensión, extensión contra la que se podría intentar hacer un ataque de fuerza bruta. La sintaxis es extremadamente simple en este caso:

svwar.py -e extension_inicio-extension_fin IP

Teniendo en cuenta que las extensiones, generalmente son de entre 3 y 4 dígitos, un ejemplo contra la PBX que tengo de pruebas podría ser:

./svwar.py -e000-9999 192.168.1.102

Hay que tener en cuenta que a mayor el rango de extensiones, mayor el tiempo invertido. A modo de curiosidad, si tenemos una sesión de consola de Asterisk abierta en la máquina objetivo (el clásico asterisk -rvvvv), veremos un sin fin de mensajes parecidos a este; un mensaje por cada extensión no existente:

svwar_ejemplo1

Al acabar el barrido, en caso de que alguna extensión le haya respondido a la herramienta, tendremos un resultado parecido al siguiente:

svwar_ejemplo2

Ya sabemos todo lo que necesitábamos saber; sabemos que la IP 192.168.1.102, tiene un Asterisk montado en el puerto 5060 y además sabemos que hay una extensión 1001, configurada en dicha centralita. Solo queda usar la tercera herramienta mencionada antes, svcrack.py para intentar obtener, si se puede, la contraseña.

Los ataques aquí se pueden hacer de dos formas, o bien a fuerza bruta mediante dígitos, o bien vía diccionario. En caso de hacerlo mediante fuerza bruta, lo que se haría sería probar diferentes combinaciones numéricas hasta acertar con el número. En caso de que en cambio se optasen por un ataque de diccionario, se tendría que tener el diccionario, en formato txt y se recurriría a él para hacer el ataque; por ejemplo, para el ataque por fuerza bruta haríamos:

./svcrack.py -u 1001 -r 0000-9999 192.168.1.102

Donde después del -u se especificaría la extensión que se desea "atacar" y después del -r especificaríamos el rango de números a utilizar como contraseña. En este caso se ha usado una contraseña débil, y desgraciadamente bastante común; la cual ha tardado apenas unos segundos en descifrar la herramienta:

svcrack_ejemplo

Para el ataque de diccionario en cambio, en vez de usar el parámetro -r, usaríamos el parámetro -d, seguido del diccionario que querríamos usar.

svcrack.py -u 1001 -d diccionario.txt 192.168.1.102

Mediante dicho método, el tiempo que tardaría en descifrar la contraseña variaría, pero en caso de usar contraseñas alfanuméricas, sería el único método posible.

COMO EVITARLO

Como siempre digo, la seguridad absoluta no existe, pero es cierto que al menos hay que intentar ponérselo difícil al atacante; es por ello que las dos primeras que hemos utilizado, para encontrar centralitas Asterisk, y para rastrear extensiones en la susodicha, se pueden evitar añadiendo una sola línea en el fichero /etc/asterisk/sip.conf:

alwaysauthreject=yes

Dicho parámetro en Asterisk 16 ya está habilitado por defecto (sin tener que siquiera añadir nada al fichero de configuración), pero en la gran mayoría de versiones no es así, con lo que es ideal ponerlo a mano en el fichero con el fin de estar seguros.

Al establecer un valor yes a dicho parámetro, lo que haríamos sería denegar la la respuesta a las autenticaciones de usuarios incorrectos o inexistentes, cosa que se hace mediante las herramientas svmap y svwar... Puede parecer un detalle sin importancia pero mediante la combinación de dicha media, junto con el uso de un puerto SIP no estandard (a poder ser un número de puerto muy alto), nos quitaríamos de un barrido el 90% de los scripts automatizados que se dedican a hacer barridos y ataques automáticos a los puertos 5060.

Eso no implica que mediante esto hagamos inmune a nuestra centralita, pero sí que evitaremos dar información al atacante, lo que hará que éste no empiece a hacer ataques de fuerza bruta contra una extensión nuestra centralita SIP, a menos que haya obtenido la información por otros medios.

Para los ataques de fuerza bruta que se hagan contra la extensión, solamente se pueden dar dos recomendaciones; la primera muy sencilla de implementar, la segunda ya depende de los medios que uno disponga.

La primera medida sería la obvia, usar contraseñas seguras para nuestras extensiones. A poder ser largas y alfanuméricas; es más al usar contraseñas alfanuméricas.

La segunda medida sería, no compleja, pero sí requeriría un cierto esfuerzo por parte de uno, y sería el uso de utilidades tales como Fail2ban, que entre otras cosas pueden bloquear los intentos repetidos de conexión/autenticación contra un destino. Hay que tener en cuenta que los ataques de fuerza bruta o de diccionario realizan un número ingente de autenticaciones, con lo que si lo podemos evitar mediante dicha herramienta podremos, dificultarle en gran medida el ataque a nuestro agresor.


Espero que os haya resultado útil, y que podáis sacarle partido a esta utilidad que puede ayudarnos a ver en si nuestra centralita esta debidamente protegida o no.

Saludos.



martes, 1 de octubre de 2019

Nftables, el sucesor de iptables

La tecnología evoluciona constantemente, a veces a costa de imposiciones de ciertos criterios, y otros a costa de comparar los pros y los contras. Iptables ha sido siempre el principal referente en el área de firewalls para Linux; es cierto que a raíz de ahí nacieron herramientas intuitivas que intentaban hacer un uso más fácil de éste, pero generalmente se usaba dicha tecnología como referencia. Obviamente, con el paso del tiempo, se han ido optando por diferentes alternativas, pero siempre intentando mantener iptables ahí, pero con la introducción de "Debian 10 (Buster)" y las últimas versiones de Red Hat, se ha ido imponiendo su sucesor, considerando al veterano firewall como obsoleto. Hablamos de nftables.

nftables_portada

Nftables es un software creado por netfilter que podría considerarse como la evolución natural de nftables, que ha intentado adaptar la sintaxis a los nuevos tiempos, con el fin de que la escritura de las reglas de este software sea algo más intuitiva que la de su predecesor.

La dinámica que utilizar este software es bastante parecida a la de iptables, en el sentido de que tal y como ocurre con su predecesor,  nftables se divide por tablas, las cuales tienen dentro cadenas, dentro de las cuales se añaden las reglas. En caso de haber usado iptables con anterioridad dichos conceptos os serán familiares, en caso contrario es importante tenerlos claros antes de continuar, ya que en caso contrario el entendimiento de la herramienta será muy difícil. Además es importante saber que las reglas de nftables se pueden definir o bien directamente en consola o bien mediante un fichero de configuración.


TABLAS

Las tablas están compuesta por una o varias cadenas; en iptables éstas seguían una lógica, ya que se agrupaban en cadenas de tipo filter, nat y mangle; en nftables podemos hacer que nuestras tablas tengan las cadenas que queramos, pero lo ideal, en términos de gestión es que sigan una lógica parecida, aunque no es obligatorio. En resumen, una tabla no es más que una o a varias cadenas agrupadas bajo un nombre común desde el cual se hacer referencia a éstas.  Las tablas pueden tener el nombre que queramos, pero es importante tener en cuenta que éstas pertenecen a diferentes familias, y que dependiendo del tipo, se tendrán en cuenta un tipo de paquete de red u otro:
  • ip: La familia por defecto, en caso de que no le digamos nosotros otra cosa. Éste solamente tendrá en cuenta los paquetes de red IPv4.
  • ip6: Como su propio nombre indica, tendrá en cuenta los paquetes IPv6.
  • inet: Una familia híbrida que contemplará ambos protocolos de IP, y que, dependiendo de a lo que hagamos referencia, tendrá en cuenta un tipo de IP u otra, o ambas, Por ejemplo si se hace referencia a una IPv4, se aplicará la regla para los paquetes IPv4, pero si hacemos referencia solamente a un puerto (por ejemplo el 80), sin especificar una IP de origen o de destino, se tendrán en cuenta ambos protocolos. Esta familia no funciona en las cadenas NAT (de esto hablaremos más adelante), al menos de momento, debido al bug 1173, con lo en caso de querer usar cadenas de dicho tipo para paquetes IPv4 e IPv6, habría que crear 2 tablas separadas, una perteneciente a la familia ip y la otra a ip6.
  • arp: Esta familia solamente tendrá en cuenta el tráfico ARP.
  • bridge: Esta familia entraría en acción en las interfaces de tipo bridge, que hayan sido creadas en el sistema.
  • netdev: Aquí se tratará el paquete antes de que sea procesado por la interfaz de red. No se tiene en cuenta si es IPv4 ni IPv6; desde aquí se ve todo, y es útil para el bloqueo de ataques DOS o DDOS, ya que lo deniega antes de ser procesado, tomándole menos tiempo al sistema el desecharlo.

La primera vez que se instala nftables, crea una tabla llamada filter del tipo inet, pero es importante conocer cómo agregar nuevas tablas. 

Para ello, antes de empezar a hacer nada, es muy recomendable cargar todos sus módulos de nftables en el kernel, ya que en caso contrario nos podremos encontrar errores tales como 'Could not process rule: No such file or directory'. Para ello lo recomendado por los propios desarrolladores del proyecto, es crear el fichero /etc/modules-load.d/modules-nftables.conf e introducir el siguiente contenido para después reiniciar el equipo:

nf_conntrack
nf_conntrack_ipv4
nf_conntrack_ipv6
nf_defrag_ipv4
nf_defrag_ipv6
nf_nat
nf_nat_ipv4
nf_tables
nf_tables_inet
nf_tables_ipv4
nf_tables_ipv6
nfnetlink
nft_counter
nft_ct
nft_hash
nft_limit
nft_log
nft_meta
nft_rbtree
nft_reject
nft_reject_inet
nft_reject_ipv4
nft_reject_ipv6

Con todos los preparativos realizados, para crear una nueva tabla escribiremos la siguiente sintaxis:

nft add table 'familia' 'nombre'

Por ejemplo, para crear una tabla llamada "filter" que contemple los paquetes IPv4 e IPv6, haremos:

nft add table inet filter

Con ello ya tendríamos nuestra primera tabla creada, tabla que puede ser borrada o que cuyo contenido puede ser listado o borrado sustituyendo add por delete, list y flush, respectivamente.


CADENAS

Las cadenas son simples contenedores de reglas. A diferencia de en iptables, que ya había una serie de cadenas predefinidas, tales como INPUT, OUTPUT, en nftables no existe ninguna por defecto, sino que tienen que ser creadas. La creación de cualquier cadena tendría una sintaxis como la siguiente:

nft add chain 'familia' 'tabla' 'nombre_cadena' {type 'tipo' hook 'estado_del_paquete' priority 'prioridad'; policy 'politica';} 

Las familias y las tablas ya las conocemos, con respecto al tipo, tenemos cadenas de tres tipos:
  • filter: Este tipo de cadena se encarga del filtrado de paquetes.
  • route: Permite rutar los paquetes salientes
  • nat: Se utiliza para aplicar reglas de nateo de paquetes, es decir, para la conversión de IPs.

Por otro lado tenemos el estado del paquete, el cual viene 'heredado' de su predecesor, iptables, y que dependiendo del tipo que hayamos escogido, se podrá hacer referencia a un estado u otro del paquete. Los estado sería input, output, forward, prerouting y postrouting. Los tres primeros son intuitivos, mientras que prerouting se utilizaría para tratar una interfaz que está a punto de ser rutada  y postrouting se utilizaría para tratar la interfaz tras haber sido rutada.  

Obviamente no todos los tipos se pueden usar en todas las familia al igual que no todos los estados se pueden usar para todos los tipos, con lo que a continuación dejo una pequeña tabla que puede servirnos como guía orientativa que nos puede servir de ayuda en la creación de cadenas:

tabla_hooks_nft

Además, podemos definir la prioridad que tendrá la cadena con respecto al resto de la misma tabla. Aquella que tenga el número más bajo (pudiendo ser negativo), será aquella que se ejecutará antes.

Además podemos también definir, de forma opcional, la política que queremos que tenga la cadena por defecto, de entres las cuales las más comunes serían: accept o drop, es decir permitir o denegar. En caso de no definir ninguna política, nftables define automáticamente que la política será accept.

Con toda esta información en mano, podemos crear varias cadenas de prueba. Por ejemplo, siguiendo la lógica del ejemplo inicial, en el que hemos creado una tabla llamada filter, vamos a crear las 3 cadenas de filtrado básicas que ya venían predefinidas en iptables, las cuales se llamaban input, output y forward. Estas cadenas, para este ejemplo, estarán abiertas por defecto, es decir que serán 3 cadenas vacías que no harán nada por defecto; solo existir y que dejarán que las reglas definidas dentro de éstos sean los que dictaminen los comportamientos:

Para crear la cadena llamada input, que se encargará de contener las reglas de los paquetes entrantes, haremos:

nft add chain inet filter input { type filter hook input priority 0\; policy accept\; }

Para las cadenas output y forward haríamos lo equivalente:

Output:
nft add chain inet filter output { type filter hook output priority 0\; policy accept\; }

Forward:
nft add chain inet filter forward { type filter hook forward priority 0\; policy accept\; }


REGLAS

Finalmente con todos estos preparativos creados, podemos empezar a crear nuestras reglas. Dichas reglas siempre tendrán que ir dentro de una cadena perteneciente a una tabla; de ahí todos los preparativos realizados hasta ahora.  Dichas reglas, pueden parecer algo abrumadoras al principio por las gran cantidad de posibilidades que ofrecen, pero a su favor hay que decir que son bastante intuitivas. La creación de reglas sigue esta sintaxis

nft add rule 'familia' 'tabla' 'cadena' 'expresión'

Inicialmente todo estaría claro a excepción de la expresión en sí, que sería lo que definiría la regla. Las expresiones son unas reglas que tienen que coincidir bajo ciertas circunstancias y que tras las cuales se deciden ciertas acciones. Esto dicho así puede parecer lioso, pero al final sería definir la regla, su argumento y la acción que se realizará al respecto... Es mejor empezar con un ejemplo sencillo que definiría la regla más sencilla posible; vamos a crear una regla que impida que puedan acceder al puerto 80. La regla se añadiría tal que así:

nft add rule inet filter input tcp dport 80 drop

En esta regla estaríamos diciendo que todo el tráfico TCP al puerto 80 sería bloqueado, y como podéis observar la expresión en sí es intuitiva. Existen muchísimas expresiones y reglas, y aquí sería imposible abarcar todas, pero las más comunes serían:

oifname 'nombre de la interfaz SALIENTE'
iifname 'nombre de la interfaz ENTRANTE'

ip protocol 'protocolo'
ip daddr 'direccion IPv4 de DESTINO'
ip saddr 'direccion IPv4 de ORIGEN'

ip6 daddr 'direccion IPv6 de DESTINO'
ip6 saddr 'direccion IPv6 de ORIGEN'

tcp dport 'puerto de DESTINO'
tcp sport 'puerto de ORIGEN'

udp dport 'puerto de DESTINO'
udp sport 'puerto de ORIGEN'

ct state 'new | established | related | invalid'

Dichas expresiones pueden combinarse entre sí dentro de la misma regla y siempre tienen que terminar con una política; las más comunes son las mencionadas antes: accept y drop. Pero existen también las políticas queue, continue y return; además de una política especial, la cual solamente se puede usar en las cadenas de tipo nat, llamada masquerade. Esta última lo que haría sería transformar la IP de origen del tráfico saliente en la IP de dicha interfaz, lo cual es común en las labores de NATeo, pues lo que se hace es transformar una IP en otra.

Usando lo de arriba como referencia podemos hacer combinaciones como estas:

Bloquear todo el trafico al puerto 22 desde el rango de IPs 192.168.1.0/24:
nft add rule inet filter input tcp dport 22 ip saddr 192.168.1.0/24 drop

Bloquear todo el trafico entrante nuevo (ct state new), proveniente del puerto 80. Es decir que nosotros podamos conectarnos a Internet, pero que no se puedan conectar a nuestro puerto 80 desde fuera:
nft add rule inet filter input ct state new drop

Bloquear los pings que accedan por la interfaz con nombre enp0s3:
nft add rule inet filter input iifname enp0s3 ip protocol icmp drop


Esto solo sería una parte de lo que puede hacer nftables, pero nos sirve para hacernos una idea global de la potencia que nos puede ofrecer esta utilidad.

Para listar todo aquello que hemos creado (las tablas, cadenas y reglas) tenemos el comando:
nft list ruleset

El cual nos mostrará nuestra configuración completa; configuración que podemos volcar en el fichero de configuración de nftables con el fin de que sea permanente. Esto se puede hacer a mano o mediante el comando:
nft list ruleset > /etc/nftables.conf

En caso de querer eliminar una regla que hayamos creado, es importante saber que, de momento, no es tan sencillo como sustituir nft add por nft delete; desgraciadamente, es necesario ejecutar primero el comando:
nft list ruleset -a

Allí veremos que al lado de cada tabla y cada regla aparecerá: #handle, seguido de un número; para eliminar la regla, tendremos que hacer referencia al handle con dicho numero. Si por ejemplo la regla de inet filter tuviese el handle 7, tendríamos que escribir:
nft delete rule inet filter input handle 7

Esto último no es del todo intuitivo y lo ideal sería poder eliminarlo de forma "natural", pero de momento es posible.


En definitiva, nos encontramos ante una versión más potente e intuitiva que iptables, que si bien ofrece varias ventajas, también posee un par de desventajas. El hecho de tener que conocer el handle de la regla para poder eliminarla, no es del todo afinado, al igual que la incapacidad de poder usar la familia inet para la tabla nat, o que sea necesario crear la tabla nat junto con sus correspondientes cadenas desde cero para hacer un simple 'masquerade' del tráfico postrouting.

Aún así es recomendable acostumbrarse a utilizarla, pues Debian 10 ya no usa iptables y en un futuro cercano nos veremos obligados a utilizar este firewall en el resto de distribuciones. Además, seguramente con el paso del tiempo irá puliendo esos pequeños detalles que hacen que esta potente herramienta no sea del todo perfecta.

Espero que os haya resultado útil.

Saludos.

lunes, 15 de enero de 2018

Cómo usar los playbooks con Ansible en Linux

Continuando con el anterior artículo, en esta ocasión vamos a profundizar un poco más en el uso de la herramienta Ansible. Hasta ahora hemos podido mandar ordenes individuales desde el controlador al resto de equipos, a los que llamaremos nodos, pero el problema radica en que por el momento lo único que estamos haciendo es mandar una orden individual a todos... Una orden que puede ser muy práctica y que puede ahorrarnos mucho tiempo, pero que no es lo más práctico cuando queremos gestionar varios servicios y tareas... Es por ello que en este aparado vamos a centrarnos en uno de los aspectos más importantes que ofrece Ansible: Los playbooks. Antes de continuar, es recomendable que, si no se tienen noción alguna de Ansible, os leáis el anterior artículo.

playbooks_portada

Un playbook no es más que un fichero que contiene las ordenes correspondientes que queremos enviar a todos los nodos; ordenes que se irían ejecutando secuencialmente según hallamos escrito en el fichero. Dicho playbook, estará escrito en lenguaje YMAL y tendrá una extensión .yml. La lógica es parecida a la seguida con las ordenes y los módulos usados en el anterior apartado, pero con más opciones y con la posibilidad de crear un conjunto de ordenes de forma estructurada y ordenada. La mejor forma de entenderlo es con un pequeño ejemplo inicial. Para ello, para tener todo bien organizado, vamos a crear un directorio llamado PLAYBOOKS dentro de /etc/ansible y dentro de dicho directorio vamos a crear un fichero llamado master.yml.
mkdir /etc/ansible/PLAYBOOKS
touch /etc/ansible/PLAYBOOKS/master.yml
Dicho fichero tendrá el contenido de a continuación:

---
#Este es el playbook maestro
- hosts: all
  remote_user: root
  tasks:
  - name: Asegurarse que NTP esta en marcha
    service: name=ntp state=started enabled=yes
...

Puede parecer complicado, pero si lo desgranamos, veremos que es muy sencillo:

  • Los primeros tres guiones indican el comienzo del fichero. Todo fichero YMAL debe llevarlo para que Ansible pueda leerlo correctamente.
  • Cualquier línea que comience con una # será considerado un comentario, tal y como ocurre en los scripts de bash.
  • Después comenzaremos con la primera sección de todas, que sería de a qué grupo (especificado previamente dentro del fichero /etc/ansible/hosts) queremos hacer referencia. Allí podemos poner el nombre de un grupo que hayamos creado o, en caso de quererlo, decir que se envíen a todas las IPs y hosts que aparezcan dentro del fichero. En nuestro caso hemos optado por dicha opción, lo cual se representa mediante - hosts: all. Es muy importante comenzar con dicho - pues marcaría el inicio de las acciones y reglas dirigidas a dicho grupo.
  • Tras decir eso, habría que decir a qué usuario remoto nos queremos conectar. Aquí podemos escoger el que queramos, pero en mi caso, ya que he preparado el usuario root del otro equipo para poder conectarme directamente a él sin contraseña alguna, optaré por usar dicho usuario.
  • Ahora habría que pasar a la parte de las tareas... Dichas tareas se definen mediante, el nombre tasks: y después debajo de éste empezaríamos a mencionar las tareas una por una; tareas que siempre empezarían con un -.
  • Toda tarea está compuesta por, cómo mínimo, 2 partes. La primera parte sería meramente informativa. Se pondría un nombre descriptivo que nos mostraría Ansible antes de ejecutar la tarea. Dicho nombre se especificaría mediante - name. La segunda parte sería la tarea a ejecutar, que constaría de: Módulo: argumentos. En este caso hemos usado el módulo service, y los argumentos usados serían name, state y enabled. Se pueden poner tantas tareas como se quieran, siempre y cuando se siga la misma estructura que la que acabo de mencionar.
  • Por último, siempre al final de cada playbook habría que escribir tres puntos.

Este ejemplo es muy sencillo y puede evolucionar a mucho más, poniendo diferentes hosts y tareas, pero sirve de buena base introductoria... Para ejecutar dicho playbook, simplemente habría que escribir el comando:
ansible-playbook /etc/ansible/PLAYBOOKS/master.yml
El contenido del playbook, al hacer referencia a una única tarea, sería lo equivalente al siguiente comando de Ansible:
ansible all -m service -a \
"name=ntp state=started enabled=yes"
Vamos a poner un ejemplo más completo en esta ocasión; supongamos que queramos asegurarnos de que el servicio NTP tenga la última versión; además también vamos a verificar que el servicio Asterisk está en marcha. Por otro lado vamos a crear una tarea exclusiva para el grupo llamado PRUEBAS; una tarea que consistirá en revisar que tenemos la última versión del paquete apache2. Al final sería aplicara los mismos conceptos que hemos visto arriba, pero de forma un poco más amplia, dejando el fichero con el siguiente aspecto:

---
#Este es el playbook maestro

#Reglas para todos los hosts
- hosts: all
  remote_user: root
  tasks:
  - name: Asegurarse que NTP esta en marcha
    service: name=ntp state=started enabled=yes
  - name: Verificar ultima version NTP
    apt: name=ntp state=latest
  - name: Asegurarse que Asterisk esta en marcha
    service: name=asterisk state=started enabled=yes

# Reglas para el grupo PRUEBAS
- hosts: PRUEBAS
  remote_user: root
  tasks:
  - name: Verificar ultima version Apache2
    service: name=apache2 state=started enabled=yes
...

Como podéis ver siempre mantendrá la misma lógica... Es decir primero irían los hosts de destino, luego el usuario con el que queremos hacer las acciones y por último las tareas a realizar.

Una característica muy interesante que podemos aprovechar en los playbooks, es el uso de handlers. Un handler es una tarea que se ejecuta únicamente en caso de que una tarea concreta (especificada por nosotros) haya realizado un cambio de estado. Por ejemplo si tuviésemos un handler preparado para cuando hubiese un cambio de estado en Apache2, y una tarea que se asegurase de que apache2 tuviese la última versión, en caso de que Apache2 no la tuviese, el handler se ejecutaría. Veamos un ejemplo usando como referencia el mencionado Apache2:

---
#Este es el playbook maestro
- hosts: all
  remote_user: root
  tasks:
  - name: Verificar ultima version Apache2
    apt: name=apache2 state=latest
    notify: "Reiniciar Apache2"
  handlers:
  - name: Reiniciar apache si es necesario
    service: name=apache2 state=restarted
    listen: "Reiniciar Apache2"
...

Como podéis observar, el handler está a la escucha de que le llegue una notificación; notificación que enviaríamos desde la tarea de verificación de la última versión de Apache2. Con lo que siempre que trabajásemos con handlers, trabajaríamos de la misma forma que con las tareas, pero con la diferencia de que desde la tarea tendríamos que notificar al handler mediante un notify, y desde el handler tendríamos que escuchar a que nos llegase una notificación concreta mediante un listen.

Gracias a las tareas (tasks) y handlers, podemos realizar playbooks muy completos donde podemos agrupar una enorme cantidad de tareas y handlers para luego así llamarlos a todos a la vez; ahorrándonos tener que escribir los comandos uno a uno para cada tarea que queramos realizar, haciendo que nos ahorremos muchísimo tiempo una vez tengamos todas las tareas correctamente definidas. Aún así, a nivel de gestión, el tener todas las tareas y handlers en un solo playbook puede ser una locura a nivel de mantenimiento... Cuando son pocas tareas y servicios no pasa nada, pero y si queremos gestionar 50 servicios, con diferentes grupos, handlers, etc... Técnicamente al agruparlo todo en un solo playbook es perfectamente viable, pero a nivel mantenerlo en el tiempo puede ser realmente costoso, especialmente si se quieren hacer cambios en el futuro... Es por ello que lo ideal es tener un playbook maestro y que este vaya llamando a diferentes playbooks dependiendo de las tareas que se quieran realizar. La llamada a dichos playbooks desde el playbook maestro se realiza mediante la sentencia include, y podemos crear una estructura perfectamente definida para que con el paso del tiempo se pueda mantener sin demasiados problemas.

Vamos a suponer que queremos poner un firewall para todos los equipos; un firewall común que todos van a tener; además también vamos a verificar que tanto Mysql como Apache2 están en marcha, pues tenemos un servidor web; por otro lado también verificaremos qué Asterisk tiene la última versión y que en dicho caso lo reiniciaremos. Estas tareas se podrían considerar como diferentes entre sí y por ello lo suyo sería tener: El playbook maestro, un playbook para el firewall, otro playbook para el apartado web y otro más para Asterisk. Comencemos con el aspecto del playbook maestro, master.yml:

---
#Este es el playbook maestro
- hosts: all
  remote_user: root
  tasks:
  - include: /etc/ansible/PLAYBOOKS/Firewall.yml
  - include: /etc/ansible/PLAYBOOKS/Web.yml
  - include: /etc/ansible/PLAYBOOKS/Asterisk.yml
  handlers:
  - include: /etc/ansible/HANDLERS/Firewall_handler.yml
  - include: /etc/ansible/HANDLERS/Web_handler.yml
  - include: /etc/ansible/HANDLERS/Asterisk_handler.yml
...

Como podéis ver todo queda alojado en ficheros separados, tanto las tareas como los handlers; handlers que como podéis ver se alojarían en otro directorio llamado /etc/ansible/HANDLERS/. Estos ficheros tendrían una ligera variación con respecto a lo que hemos visto hasta ahora, pues a diferencia de un playbook "normal", en estos no haría falta ponerles las etiquetas, solamente las tareas a realizar, pues ya estarían asignadas previamente por el master.yml. Veamos por ejemplo el contenido de uno de los playbooks de tareas; concretamente el contenido del fichero Firewall.yml que posee un módulo que no hemos visto hasta ahora; el módulo copy en el que copiaremos un fichero desde nuestro controlador al resto de nodos.

---
#ESTE ES EL PLAYBOOK DEL FIREWALL
- name: COPIAR IPTABLES DEL CONTROLADOR A LOS NODOS
  copy: src=/usr/src/iptables.sh dest=/etc/init.d/iptables.sh
  notify: "CORTAFUEGOS"
...

Como veis directamente empezaríamos con el - name; sin mencionar los hosts, ni el usuario remoto, ni el hecho de que es una tarea y no un handler, pues todo eso ya ha sido especificado por el anterior playbook. En este caso lo que hacemos simplemente es copiar un fichero desde la máquina controladora al resto de equipos y que en caso de que haya una diferencia entre el iptables.sh del equipo controlador y el del nodo, mande un notify llamado "CORTAFUEGOS". Obviamente si el fichero iptables.sh no existiese en el nodo, también mandaría dicho notify.

El proceso de llamamiento de un handler mediante un include no difiere apenas con el realizado con las tareas. Buen ejemplo de ello sería el handler Firewall_handler.yml que estaría relacionado con la tarea arriba mostrada; en este caso usaremos otro módulo nuevo llamado file, capaz de modificar los permisos de un fichero para que sea ejecutable:

---
#HANDLERS FIREWALL
- name: Dar permisos firewall
  file: dest=/etc/init.d/iptables.sh mode=755 state=touch
  listen: "CORTAFUEGOS"
- name: Arrancar cortafuegos
  shell: /etc/init.d/iptables.sh start
  listen: "CORTAFUEGOS"
...

En este caso el handler lo primero que haría sería darle los permisos de ejecución necesarios al nuevo script para luego ejecutarlo. El contenido del cortafuegos no es algo relevante para este ejemplo, pero a modo de prueba podría ser algo como lo siguiente:


#!/bin/bash
case "$1" in
        start)
                iptables -F
                iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
                iptables -A INPUT -m state --state NEW -j DROP
                iptables -A OUTPUT -j ACCEPT
        ;;
        stop)
                iptables -F
        ;;
esac

Con esto ya tendríamos el firewall controlado y simplemente siguiendo las mismas pautas se podría hacer exactamente lo mismo para la gestión de tanto la parte Web como Asterisk, pues la base sería la misma que la que hemos estado usando hasta ahora.

Gracias a lo que hemos visto en este artículo, tendríamos un dominio básico de los playbooks gracias al cual podríamos realizar la mayoría de las gestiones necesarias para controlar un grupo de servidores... De aquí en adelante solo sería profundizar conceptos y jugar con los diferentes módulos de Ansible, con el fin de tener los playbooks más óptimos posibles.

Espero que os haya resultado útil.

Saludos.

miércoles, 9 de noviembre de 2016

Cómo configurar una DMZ con Linux

Uno de los aspectos más preocupantes de la seguridad es la seguridad a nivel de red; ya que es el área en la que los equipos están más expuestos y en la que hay más probabilidades de que puedan tomar el control de nuestro equipo. Obviamente hay más "puertas" para acceder a un equipo; hay un dicho bastante popular que dice que la seguridad perfecta únicamente se puede lograr teniendo un equipo dentro de una jaula de Faraday sin conexión a Internet y rezando de que nadie te robe la llave para abrir dicha jaula... Pero es innegable que generalmente se trata de acceder a un equipo remotamente a nivel de red... Es por eso que a veces hay que considerar la posibilidad de que un equipo pueda ser vulnerado y de que de ahí traten de "saltar" a otros equipos con los que dicho equipo tenga comunicación; he ahí donde entra en juego un concepto muy importante y útil llamado DMZ.

DMZ_portada

Una DMZ (Demilitarized Zone) es una zona "especial" que está expuesta a la red que es propensa a recibir ataques de los cyber-criminales; esta "zona" esta compuesta por servidores de correo, DNS y web entre otros; es decir servidores que tienen que estar expuestos al público. Lo ideal es que jamás de los jamases puedan vulnerar ninguno de éstos, pero esta situación es utópica y no podemos simplemente esperar a que no ocurra nunca, ya sea debido a que pensamos nuestras medidas de seguridad son muy buenas o debido a que pensamos que nunca se van a fijar en nuestros servidores; es por eso que nos tenemos que poner en la peor situación posible; es decir la situación de que consigan acceder a uno de estos servidores... situación en la que cobra sentido el uso de las DMZ debido a que su función es tan sencilla como que permita el acceso desde el exterior a esta zona, que desde el interior de la red "corporativa" también se pueda acceder ésta, pero que desde la DMZ no se pueda acceder a la red corporativa; que esté aislada. Para entender mejor este ejemplo he aquí este pequeño esquema:

esquema_DMZ

Como veis el objetivo es muy claro y a nivel gráfico es, en mi opinión, bastante entendible... La cuestión está que generalmente para lograr dicho propósito requeriríamos de un cortafuegos o un switch con estas capacidades... Cosa que generalmente requiere de una inversión económica importante y que a veces no nos es posible realizar. Afortunadamente podemos optar por realizar un DMZ con un equipo que simplemente tenga 3 tarjetas de red y Linux instalado. En concreto con la misma configuración que la mostrada en el esquema de arriba; es decir:

  • Una interfaz de red de nombre eth0 con ip 192.168.1.2 y máscara 255.255.255.0 que estaría conectada al router.
  • Una interfaz de red de nombre eth1 con ip 192.168.2,1 y máscara 255.255.255.0 que estaría conectada a la DMZ, es decir al área que va a recibir un tratamiento "especial".
  • Una interfaz de red de nombre eth2 con ip 192.168.3.1 y máscara 255.255.255.0 que estaría conectada a la red local.

Teniendo esta disposición de interfaces de red clara, tenemos que tener claro qué objetivo lograr y cómo realizarlo... Con el esquema de arriba y las interfaces de red claras, y a sabiendas del objetivo de una DMZ veríamos que el objetivo sería:

  • Que las peticiones a ciertos puertos de la interfaz eth0, sean redirigidos a eth1 (DMZ).
  • Que el tráfico que venga desde la interfaz eth0 hacia la interfaz eth1, pase sin ningún problema.
  • Que el tráfico que vaya desde la interfaz eth1 a eth0, sea únicamente el tráfico de respuesta a las peticiones que hayan realizado a los servidores de la DMZ.
  • Que el tráfico que provenga desde la interfaz eth2 hacia al interfaz eth1, pase también sin ningún problema.
  • Que el único tráfico que puede venir desde la DMZ (eth1) hasta la red local, sea el originado por respuestas a las peticiones realizadas desde la red local (eth2).

¿Todo esto como lo podríamos lograr? Con iptables por supuesto. Todo lo comentado arriba lo lograríamos con este simple script que únicamente habría que ejecutar para tener nuestra DMZ operativa. Hay que tener en cuenta que esta configuración estaría pensada para una DMZ que tenga un servidor web y uno de DNS; es decir que estaría preparado para redirigir las peticiones a los puertos 53 y 80. El script en cuestión podría llamarse DMZ.sh

  1. #!/bin/bash
  2. #PREPARAMOS LAS REGLAS BASICAS
  3. echo 1 > /proc/sys/net/ipv4/ip_forward
  4. iptables -F
  5. iptables -t nat -F
  6. iptables -A INPUT -j DROP
  7. iptables -A FORWARD -j DROP
  8. iptables -A OUTPUT -j ACCEPT
  9. #PREPARAMOS LAS REGLAS DE FORWARD
  10. #DE ETH0 A ETH1 DEJAMOS QUE PASE TODO EL TRAFICO
  11. iptables -A FORWARD -i eth0 -o eth1 -s state --state NEW,ESTABLISHED,RELATED -j ACCEPT
  12. #DE ETH1 A ETH0 DEJAMOS QUE SOLAMENTE PASEN LAS RESPUESTAS A LAS PETICIONES
  13. iptables -A FORWARD -i eth1 -o eth0 -s state --state ESTABLISHED,RELATED -j ACCEPT
  14. #DE ETH2 A ETH1 DEJAMOS QUE PASE TODO EL TRAFICO
  15. iptables -A FORWARD -i eth2 -o eth1 -s state --state NEW,ESTABLISHED,RELATED -j ACCEPT
  16. #DE ETH1 A ETH2 DEJAMOS QUE SOLAMENTE PASEN LAS RESPUESTAS A LAS PETICIONES
  17. iptables -A FORWARD -i eth1 -o eth2 -s state --state ESTABLISHED,RELATED -j ACCEPT
  18. #PREPARAMOS LAS REDIRECCIONES DESDE EL EXTERIOR A LA DMZ
  19. #PUERTOS REDIRIGIDOS 53 Y 80
  20. #IPS FICTICIAS PERO QUE ESTAN DENTRO DEL RANGO DE ETH1
  21. #PUERTO 53, TCP Y UDP
  22. iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 53 -j DNAT --to 192.168.2.4:53
  23. iptables -t nat -A PREROUTING -i eth0 -p udp --dport 53 -j DNAT --to 192.168.2.4:53
  24. #PUERTO 80 TCP: WEB
  25. iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to 192.168.2.5:80

Con este pequeño script ya tendríamos el equipo preparado para actuar de "firewall" y para convertir la zona conectada a la interfaz eth1 en una DMZ.

Como veis la creación de este tipo de entornos no es excesivamente complicada, simplemente requiere tener ciertas bases de iptables para poder desenvolvernos con libertad; lo cual demuestra, una vez más, la enorme utilidad de esta herramienta.

Espero que os haya resultado útil.

Saludos.

viernes, 16 de septiembre de 2016

Cómo filtrar accesos no deseados en Linux por países

El tener un equipo expuesto en la red se ha convertido en una práctica cada vez más extendida, y obviamente ello a hecho que también se convierta en una práctica cada vez más peligrosa. Los cybercriminales no cesan de intentar vulnerar diferentes servicios expuestos en la red: HTTP, SSH, Asterisk, telnet, FTP... Generalmente uno piensa ¿Quien va a querer hackearme a mí? Pero hemos de tener en cuenta que hay una gran cantidad de ataques que son efectuados por herramientas automáticas que simplemente buscan IPs en la red e intentan acceder a ciertos servicios de éstas, indistintamente de si se es un particular o una empresa. Las técnicas de ataque van evolucionando con el tiempo, siendo cada vez más sutiles y efectivas, con lo cual las defensas tienen que ser equiparables para poder salvaguardar los servicios expuestos en Internet.  Una buena medida es bloquear el acceso dependiendo del país de origen de la conexión.

firewall_country

Generalmente deseamos que todo el mundo pueda acceder a nuestro contenido desde cualquier origen, especialmente si hablamos de una web, pero hay ciertos países que tienen fama por realizar cyber-intrusiones en sistemas ajenos y que dependiendo del contenido o idioma de nuestra web, no tiene sentido que accedan; me explico: Este blog está escrito en castellano al 100% y si bien es uno de los idiomas más hablados del mundo, hay países que por un lado tienen fama por vulnerar sistemas informáticos, y que por otro lado poseen una tasa muy baja de hispanohablantes, por no decir que casi nula. Dos de esos países que tienen un gran fama por cumplir ambas condiciones serían Rusia y China, con lo que ver conexiones con dichos países de origen sería bastante inusual... Es más, hay varios sitios que, estén el idioma en el que estén, suelen bloquear esos países debido a su alta tasa de ataques cybernéticos, cosa que es más sencilla de lo que parece gracias a una combinación de iptables y xtables_addons.

Xtables_addons son una serie de complementos que se aplican sobre el kernel y que hacen que iptables tenga un mayor número de funcionalidades de las que ya de por sí traen (que no son pocas). Estos complementos se pueden instalar directamente gracias a los repositorios oficiales del sistema escribiendo lo siguiente:

apt-get install libtext-csv-xs-perl xtables-addons-common xtables-addons-dkms

Esto hará que instalemos todas las dependencias necesarias para poder usar los addons, en concreto uno de ellos que nos interesa especialmente llamado GeoIP y que asocia unos rangos de IPs con sus respectivos países. Aún así, si bien la funcionalidad está preparada, no contiene dato alguno con el que poder reconocer qué ip está asociada a qué país; es por eso que lo primero que tendremos que hacer será descargarnos la base de datos que contiene dicha información; base de datos que no tendremos que descargarnos desde ninguna web, sino que podemos descargarnos desde un script:

  1. cd /usr/lib/xtables-addons/
  2. ./xt_geoip_dl

Este proceso nos descargará un fichero en formato .csv llamado GeoIPCountryWhois.csv que contendrá toda la información; el problema radica en qué dicha información no es reconocible por iptables ni por xtables_addons, y es por ello es necesario hacerla "legible" para ambas herramientas. Para ello primero hay que crear un directorio llamado LE dentro del directorio /usr/share/xt_geoip para después ejecutar el comando que hará el csv legible y que hará que la información correspondiente se guarde dentro del directorio que hemos creado:

  1. mkdir /usr/share/xt_geoip/LE
  2. /usr/lib/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip/ GeoIPCountryWhois.csv

Este proceso mostrará una salida en la que se verán los países junto con el número de IPs asociados a éstas, IPs que pueden ser tanto IPv4 como IPv6... El listado mostrado es enorme, pero he aquí un pequeño extracto para que os hagáis una idea de la salida del último comando:

BD_GeoIP

Ahora tanto iptables como xtables son perfectamente capaces de asociar las IPs a sus respectivos países, con lo que llegaría el turno de añadir nuevos filtros que nos proporcionen cierta seguridad. En este caso nos encargaremos de filtrar tanto los proxies anónimos como los países Rusia y China. Para ello combinaremos las reglas habituales de iptables junto con las características especiales ofrecidas por los addons instalados. Toda regla que incluya la característica GeoIP, tendrá las siguiente estructura básica:

iptables -A INPUT -m geoip --src-cc $SIGLAS_ORIGEN -j $ACCION

Al hacer referencia a las siglas del origen, estaríamos haciendo referencia al origen de la conexión. Por ejemplo los proxies anónimos tendrían las siglas "A1", mientras que china tendría las siglas "CN". La acción a tomar quedaría a nuestra elección, pero generalmente sería o ACCEPT (para aceptar la conexión de dicho origen) y DROP (denegar dicho acceso). Partiendo de la base de que vamos a aceptar las conexiones de todos lados a excepción de unos lugares concretos usaremos la acción DROP para el ejemplo de a continuación.

  1. iptables -A INPUT -p tcp --dport 21:23 -m geoip --src-cc A1 -j DROP
  2. iptables -A INPUT -m geoip --src-cc CN-j DROP
  3. iptables -A INPUT -p udp --dport 5060 -m geoip --src-cc RU -j DROP

En este pequeño ejemplo estaríamos haciendo que no se pudiese acceder a los puertos 21,22 y 23 (protocolo TCP) desde proxies anónimos, que ninguna conexión procedente de China fuese posible y que todo intento de conexión hacia el puerto 5060 (protocolo UDP) procedente desde Rusia fuese posible. Obviamente estas son meras reglas de ejemplo y cada uno podría elegir el país que desee y las reglas de filtrado que más se adapten a sus necesidades.

Como podéis observar el filtrar conexiones por países no es complicado; si bien la primera instalación es algo incomoda, la gestión de las reglas del cortafuegos tras dicha instalación es una tarea muy cómoda que cualquier que tenga ciertas nociones de iptables puede realizar sin problema alguno.

Espero que os haya sido útil.

Saludos.