No es difícil suponer que cuando tienes la escala de Google te encuentras con problemas que el resto no tenemos, o que sí tendremos, pero en unos años. Es algo que ya ha pasado con un buen número de tecnologías, desde MapReduce hasta Kubernetes y que, quién sabe, también pasará con el tema de este post. Esta vez nos centramos en el modelo de seguridad tradicional que conoce todo el mundo. Acceso limitado a la red perimetral a través de firewalls, redes DMZ, VPNs y similares son algo que todos hemos usado.

Una de las críticas, diría que con razón, que se hacen a este modelo es que una vez se accede a un nivel, todas las máquinas de esa capa quedan a la vista. De esta manera, es probable que un administrador de base de datos tenga acceso a servidores de almacenamiento o red, o a todas las máquinas de un mismo dominio de Windows. Algo a tener en cuenta dada la necesidad que parece que tenemos de pulsar sobre mensajes que nos mandan príncipes nigerianos.

Por supuesto, esta no deja de ser una simplificación. En la práctica se pueden securizar más estos niveles, se pueden usar tecnologías tipo IPSEC y similares, o se puede tener un control más específico de quién accede a dónde. Hoy en día, a menudo, aunque se conzca el problema de base, cuando la cantidad de gente que trabaja en estos entornos es limitada y controlada, puede parecer razonable asumir el riesgo en relación al coste de cualquier otra medida.

Claro, cuando la escala es lo suficientemente grande, el control se hace más complicado. Hay muchos servicios, de todo tipo; algunos se gestionan vía SSH, otros vía Web, o con APIs específicos. En cuanto a los administradores, además de los propios responsables de las plataformas, puede haber personal externo, estudiantes en prácticas, personal de atención al cliente, y tantos otros. Para terminar, el acceso también es más diverso. Lo que antes era un PC en una oficina, ahora puede ser un PC, o un portátil en casa, o una tablet desde la Wifi del tren.

Con estos antecedentes, y dados los ataques y problemas propios del entorno, Google comenzó un proyecto interno para cambiar su gestión de la seguridad. Evidentemente, los recursos y el nivel técnico de estas "grandes" les permiten llegar a desarrollos que para el resto son complicados de alcanzar, pero los conceptos más generales siempre pueden adaptarse y, llegado el momento, implementarse en entornos más pequeños.

En este post voy a hacer un resumen básico de lo que se busca con BeyondCorp, que es como ha llamado Google a su proyecto interno, pero también me gustaría ver qué software podríamos usar para hacer una implementación, aunque sea básica, de las ideas principales que se buscan. Si queréis dejar de leer ahora y pasar a documentación más completa, estos enlaces pueden serviros:

  • BeyondCorp: Web con información básica y enlaces a algunos artículos técnicos. Está desarrollada por ScaleFT, una de las empresas que han creado un producto en la línea de los documentos publicados por Google, y que están documentados en la propia web. Son fáciles de leer, os los recomiendo.
  • Google BeyondCorp: Web de referencia en Google Cloud.
  • Zero Trust Networks: Uno de los pocos libros que he visto sobre la materia. Son poco más de 200 páginas, y siempre podréis sacar conclusiones y ver qué os sirve y qué no.

¿Qué son las redes Zero Trust?

Una red Zero Trust es una red en la que no se confía. Por lo tanto, todos los dispositivos que están el ella se gestionan como si estuviesen directamente conectados a Internet (que no deja de ser una red de este tipo), independientemente de si dan servicio externo o interno. En definitiva, se trata de complementar todo aquello para lo que el modelo tradicional es efectivo, añadiendo medidas contra lo que los estándares actuales, la segmentación de red, DMZs y similares son más limitados:

  • Equipos comprometidos por Phising, Keyloggers, ...
  • Shells inversos en servidores con aplicaciones no actualizadas
  • Movimiento lateral entre equipos de un mismo segmento de red
  • Contraseñas vulnerables
  • Acceso a servidores internos desde servidores públicos vulnerables
  • ...

Las redes Zero Trust se fundamentan en lo siguiente:

  • Asumir que el tráfico dentro de un centro de datos o de una red corporativa es seguro es erróneo. La segmentación en zonas y la seguridad perimetral que se ha venido siguiendo estos años no impiden los movimientos laterales dentro de un mismo nivel en el momento en el que un servidor o dispositivo se vea comprometido.
  • Todas las redes deben considerarse hostiles.
  • Las amenazas pueden ser tanto internas como externas.
  • Que un dispositivo pertenezca a un segmento de red no es suficiente para garantizar la confianza.
  • Todos los dispositivos, usuarios y flujo de red deben estar autorizados y autenticados.
  • Las políticas de acceso deben ser dinámicas.

Elementos principales

Independientemente de cómo se implemente, una parte importante del trabajo va a ser la capa de control. Hablamos del entorno que va a identificar a usuarios y dispositivos, y que va a autorizar el acceso a los elementos de la red. Idealmente, se va a formar una entidad con el dispositivo desde el que se accede, el usuario que lo está intentando, y la aplicación. A partir de aquí, el sistema tomará una decisión basada en las variables que se consideren adecuadas, y que pueden ir en la línea de las siguientes:

  • ¿Se está accediendo desde un equipo inventariado?
  • ¿El equipo tiene todas las actualizaciones de seguridad instaladas?
  • ¿Desde dónde se está intentando acceder?
  • ¿A qué hora?
  • ¿Qué usuario está intentando acceder?
  • ¿A qué aplicación o servidor se quiere acceder?
  • ¿Qué método está usando para acceder? ¿Qué algoritmo de seguridad?

En realidad, esta lista se podría completar con todo tipo de información adicional. El objetivo es que este sistema de control use estos datos para tomar una decisión (permitir o no), y que abra el camino para que se pueda establecer la conexión, de forma dinámica, entre ese cliente (y solo ese cliente) y el destino (y solo ese destino).

¿Cómo autorizamos un intento de acceso?

Por lo general, al hablar de autenticación hablamos de algún tipo de cifrado. En definitiva, se trata de intentar garantizar que un dispositivo origen puede acceder a un recurso, y para esto, estándares tan desarrollados como los certificados basados en X.509 son una elección lógica. Es relativamente común que se usen CAs privadas para crear los certificados que se usan en una organización.

Como siempre, alrededor de este concepto se pueden desarrollar todo tipo de sistemas avanzados que controlan la validez de los certificados, el inventario, y todo lo que se os ocurra. Hay gente que crea certificados válidos por poco tiempo, otros usan ocsp, y también los hay que incluyen información que puede ser útil en la toma de decisiones en alguno de los atributos que define el protocolo.

¿Cómo autenticamos un intento de acceso?

Ojo, que autorizar y autenticar no son lo mismo. En su forma más simple, podemos estar hablando de un formulario web que se enganche a una base de datos de usuarios, sea del tipo que sea. Por lo tanto, un usuario se conectará desde un equipo autorizado, con las credenciales que tenga asignadas en ese momento.

Una vez más, a partir de esta idea básica se pueden hacer todo tipo de desarrollos y comprobaciones. Con los datos que tenemos ya es esta fase, podremos mostrar un listado de servidores a los que permitir la conexión, topes de tiempo para acceder, obtener datos de otros sistemas internos, como puede ser de monitorización, y tantos otros.

¿Cómo hacemos efectivo el acceso?

En estos últimos años la automatización ha sido una de las tecnologías que más han avanzado, y diría que es una de las claves que permiten implementar un sistema Zero Trust con facilidad. En su forma más sencilla, se puede usar ansible, chef, puppet, cfengine y similares para activar los permisos necesarios. Sistemas más avanzados permitirían, además, la configuración de accesos a nivel de red, vlanes o enrutamiento, en función de la infraestructura con la que se trabaje.

Implementación de ejemplo

Cuando se lee documentación de este tipo de tecnologías se tiende a concretar poco el software que podría usarse para implementarlas. Hagamos una prueba de concepto, aunque sin escribir código, y siempre limitándonos a lo más básico.

Uno de los ejes fundamentales sobre el que gira la tecnología Zero Trust es el cifrado. Para conseguirlo, nos interesaría tener una buena gestión de certificados. Hacer un inventario no es trivial, y mucho menos si queremos asociar de manera efectiva los portátiles (por ejemplo) en los que se instalan, con los usuarios a los que pertenecen. Si somos creativos, podemos hacer cosas muy sencillas pero que pueden ser interesantes, como por ejemplo definiendo atributos extra (los certificados X.509 permiten añadir cosas como teléfonos, estados, y muchos otros), o usando números de serie para los certificados. Imaginad, por ejemplo, que llevamos el control de un certificado que pertenece a un usuario (en base a los datos del Subject), y que incrementamos el número de serie cada vez que haya una actualización de seguridad. El usuario se instalará el nuevo certificado cuando actualice su equipo, y el sistema solo validará la sesión SSL si es así. Es algo simple, pero puede ser un buen punto de partida.

# openssl req -new -key client_new.key -out client_new.csr
-----
Country Name (2 letter code) [AU]:??
State or Province Name (full name) [Some-State]:????
Locality Name (eg, city) []:????
Organization Name (eg, company) [Internet Widgits Pty Ltd]:????
Organizational Unit Name (eg, section) []:????
Common Name (e.g. server FQDN or YOUR name) []:usuario
Email Address []:usuario@example.com
--- No es difícil incluir más atributos aquí

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

#root@ansible:~# openssl x509 -req -days 365 -in client_new.csr -CA ca.cert -CAkey ca.key -set_serial 1 -out client_new.crt

Enseguida veremos que los servidores web o los proxies modernos pueden leer estos valores.

Incluso en los casos en los que la aplicación que usemos no funcione con certificados, los navegadores, en su inmensa mayoría, sí los usan. Gracias a esto podríamos generar las reglas de acceso necesarias para un cliente desde el navegador, y permitir el acceso a esa otra aplicación con la confianza suficiente de que se origina desde un cliente válido. Las implementaciones más avanzadas usan proxies, por ejemplo de SSH, o tienen utilidades que cambian, en el caso de SSH, la configuración de los clientes y del servidor para que usen los nuevos certificados. El termino medio que planteo basa la autenticación y la autorización en un interfaz web muy compatible con los certificados, para que luego el cliente pueda conectarse al servidor de la forma más natural, que en el caso de SSH no dejan de ser las claves públicas y privadas de toda la vida. No es algo perfecto, ni mucho menos, pero es genérico y no obliga a reconfigurar los servidores constantemente.

Pasemos a lo que puede ser el interfaz web con la lógica necesaria para el sistema.

Queremos controlar el estado de los certificados en detalle, y para eso no hay nada como Nginx y su módulo para Lua Este no es un post sobre Nginx, así que solo daré algunas pistas sobre cómo se podría implementar todo esto sobre lo que estamos hablando con Nginx, pero hasta ahí.

Como sabéis los que conocéis Nginx, los certificados de servidor "de siempre" se leen con directivas como estas:

ssl_certificate /ruta/certificado_servicio;
ssl_certificate_key /ruta/clave_servicio;

Y se puede obligar al uso de un certificado cliente, instalado en el navegador, que se haya firmado con una CA

ssl_client_certificate      /ruta/certificado_ca;
ssl_verify_client           on;

Con esto ya sabremos que solo se accederá al sitio desde equipos en los que se haya instalado un certificado que hayamos firmado con nuestra CA. Lo que no hemos hecho es ampliar la lógica y usar el número de serie o el Subject del certificado. No hay problema, Nginx crea variables con todo lo que necesitamos, y que podemos usar en la fase de acceso de los distintos location. Si usamos el módulo Lua, no habrá ningún problema para acceder a los datos que nos manda el navegador, y para aplicar la lógica que queramos con ellos. Podemos leer datos de inventario desde una base de datos, desde redis, en un json desde un servidor independiente, .... Hay librerías para todos los gustos.

location ??? {
  access_by_lua_block {
    -- En la variable $ssl_client_s_dn tenemos un cadena de texto con el email, el CN, OU, O, L, ST, C y otros atributos menos habituales que hayamos configurado
    local dn = ngx.var.ssl_client_s_dn

    -- En la variable $ssl_client_serial tenemos el numero de serie del certificado del cliente.
    local serial = ngx.var.ssl_client_serial

    -- A partir de aqui, accedemos a donde sea que tengamos la gestion del inventario, y permitimos el acceso, o no
    -- ngx.exit(403)
    -- ngx.exit(ngx.OK)
  }
}

La autenticación es básicamente cosa de programación, del estilo de lo que hemos hecho siempre. Una ventana de acceso, login, password con credenciales del usuario, y todo lo demás. Esto se complementaría con el bloque de control de certificado que hemos descrito antes. Además, siempre se pueden incluir ventanas adicionales en las que un usuario autenticado vea los host a los que puede acceder, y quizá puedan definir una franja horaria en la que van a conectarse a ese host o grupo de hosts.

La idea es garantizar que el acceso se ha hecho desde un equipo autorizado, y que se ha hecho por un usuario autenticado. Al final de todo, podremos haber generado una sesión típica con el navegador cliente. Esta sesión puede servir si usamos este servidor como proxy para accesos web. Imaginad, por ejemplo, que queremos que los accesos a wp-login.php de un Wordpress solo puedan realizarse a través de este sistema. Solo necesitamos las reglas básicas para que Nginx haga de proxy a ese destino, controlando la validez de la sesión. La ventaja es que el Wordpress no necesita ningún tipo de modificación, ya que es el proxy de autenticación el encargado de controlar los accesos en este nivel.

Para mantener las cosas simples, imaginad que solo tenemos un número determinado de Wordpress que queremos controlar. De no ser así, esta parte también podría ser dinámica y programada sin mayor dificultad; en Nginx se puede usar Lua para elegir upstream.

upstream wordpress {
  ip_hash;
  server ip_wordpress;

Tendríamos una serie de location para gestionar el login, con sus métodos GET y POST, luego tendríamos las ventanas adicionales. Al final tendríamos otro location que haría el proxy_pass. Hay muchas formas de cifrar sesiones y cookies en Nginx. No trato de enseñarlas aquí o de hacer un bloque completo. Es solo una idea básica.

location = /wp-login.php {
  set $dominio 'example.com';

  access_by_lua_block {
    -- Que no se olvide la parte de autorizacion en base al certificado
    --
    -- Lo normal es que usasemos un POST para recibir el host al que redirigir, y que usasemos balancer_by_lua para elegir dinamicamente el upstream
    --
    if ngx.var.request_method == "GET" then
      return
    elseif ngx.var.request_method ~= "POST" then
      ngx.exit(403)
    end
    local ck = require "resty.cookie"
    local cookie, err = ck:new()
    if not cookie then
      ngx.exit(403)
    end

    -- Un usuarios validado con todo OK tendria una cookie AUTHSESSION (por ejemplo) cifrada que usariamos para verificar que el acceso esta permitido
    local authsession = cookie:get("AUTHSESSION")
    if not authsession then
      -- No permitimos si no hay cookie
      ngx.exit(403)
    end
    --
    -- Es un ejemplo basico de uso de cookies, pero hay otras alternativas
    -- Tenemos cookie AUTHSESSION, la descifrariamos y, si fuese ok, seguimos adelante
    -- Tambien hay muchas opciones para ver TTLs y confirmar que no ha expirado
    --
    -- Si llegase el host en un POST, podemos leerlo sin problemas
    ngx.req.read_body()
    local args, err = ngx.req.get_post_args()
    -- En args estan los parametros que han llegado en el POST
    -- Si todas las comprobaciones fuesen bien, hariamos el proxy_pass
    return
  }

  proxy_pass https://wordpress;
  proxy_set_header Host $dominio;
  proxy_set_header X-Real-IP $remote_addr;
}

location / {
  set $dominio 'example.com';

  proxy_pass https://wordpress;
  proxy_set_header Host $dominio;
  proxy_set_header X-Real-IP $remote_addr;
}

Falta mucho código, pero no quiero convertir esto en un post sobre Nginx/Lua. La idea que quiero transmitir es que podemos meter un proxy entre nuestro destino (nos centramos en HTTP en este caso) y el cliente. La única modificación que hay que hacer al Apache o lo que sea que gestione el Wordpress es limitar los accesos a wp-login.php, para que solo se pueda llegar desde el proxy de acceso. De esta manera, quien quiera administrarlo tendrá que pasar por todo el proceso de validación, pero sería transparente para el resto de usuarios.

Para las aplicaciones basadas en HTTP es fácil gestionar un proxy. Para el resto, este tipo de proxies pueden ser más complicados, aunque en este momento se está terminando el soporte Lua para el módulo stream que permitirá hacer proxies muy interesantes.

En cualquier caso, como he comentado al principio, es fácil complementar el control de accesos a, por ejemplo, SSH, usando la misma idea que hemos usado hasta ahora. Un usuario puede usar su navegador desde su portatil, y Nginx puede crear una petición a un servicio externo que sincronice la clave pública y cree una regla iptables que habilite el acceso. Otro proceso podría, vía cron por ejemplo, revocar ese acceso y esa regla llegado el momento.

Si optasemos por ansible sería muy sencillo hacer un pequeño servidor en flask o tornado que usasen el api python del propio ansible para lanzar las tareas que habilitan el acceso. En Nginx tendríamos un nuevo upstream, tipo:

upstream tornado {
  ip_hash;
  server 127.0.0.1:puerto;

y un location parecido al que hemos escrito para wp-login.php haría una llamada a ese tornado que lanzaría las tareas de ansible contra los módulos authorized_key e iptables. Con Nginx/Lua, crear una petición para un upstream es algo trivial. Aquí van unas pistas para los interesados:

location = /acceso_ssh.html {
  set $dominio 'example.com';

  access_by_lua_block {
    -- Que no se olvide la parte de autorizacion en base al certificado
    --
    -- En el POST recibiríamos el servidor destino, la IP origen, y
    -- cualquier otro dato necesario para ansible
    --
    -- Por supuesto, toda la parte de la sesion de navegador sigue
    -- siendo necesaria
    --
    -- Imaginemos que tenemos el host remoto en la variable hostremoto
    -- del formulario que nos llega en el POST
    -- El modulo Lua recibe la IP origen en la variable ngx.var.remote_addr
    -- Podriamos tener en inventario las claves publicas de los usuarios
    --
    ngx.req.read_body()
    if not args then
      ngx.exit(403)
    end
    local args, err = ngx.req.get_post_args()
    local iporigen = ngx.var.remote_addr
    local host_remoto = args["hostremoto"]
    local clave_ssh = args["clave_publica"]

    -- Simulemos que el servidor tornado recibe un json en el cuerpo, por cambiar

    local habilitar_acceso = {}
    habilitar_acceso["ip"] = iporigen
    habilitar_acceso["servidor"] = host_remoto
    habilitar_acceso["clave"] = clave_ssh

    ngx.req.set_body_data(cjson.encode(habilitar_acceso))

    res = ngx.location.capture("/habilitar_ssh", {method = ngx.HTTP_POST})

    -- Si tornado devuelve un codigo HTTP diferente a 200 es un error
    if res.status ~= 200 then
      ngx.exit(403)
    end

    return
  }

  # Si todo ha ido bien el acceso se habra creado (via tornado) y podremos mostrar un html
  # o lo que sea con un mensaje de OK. Podria ser un html estatico o generado
  # via content_by_lua
}

location = /habilitar_ssh {
  # A este location llega el POST con el cuerpo creado a partir
  # de la tabla habilitar_acceso, formateado como json

  internal;
  # Imaginemos que tenemos este endpoint en tornado donde se llama a ansible
  # Tendria que implementar POST, y leer y parsear el json
  # Devolveria un codigo HTTP 200 si todo hubiese ido bien
  proxy_pass http://tornado/habilitar_ssh;
}

Conclusiones

La verdad es que he dudado de si interesaba escribir código en este post. Evidentemente, no estamos hablando de un proyecto de fin de semana, así que, lo que he escrito, en realidad, no sirve para nada. Sin embargo, tampoco siempre se lee documentación con detalles concretos, así que esto podría dar alguna idea a alguien.

En cualquier caso, viniendo en cierta medida de Google (en realidad los conceptos no son nuevos, en absoluto), no sería raro que las palabras "Zero Trust" se vayan escuchando cada vez más, y que se hagan más desarrollos.

En definitiva, aunque la parte del código sea poco útil, espero que las ideas que hay detrás de todo esto hayan quedado claras. Se lo preguntaré a las tres personas que van a leer el post.


Port Knocking sin complicaciones

mié, 06 nov 2013 by Foron

(Así, pecando de básico desde el principio)

Históricamente, desde el punto de vista de la seguridad, los servidores han venido teniendo dos tipos de puertos:

  • Los que tienen que estar siempre abiertos: Los puertos HTTP o HTTPS de un servidor web, sin ir más lejos.
  • Los que sólo tienen que estar abiertos para unas IPs determinadas: SSH, por ejemplo.

La configuración, en ambos casos, siempre ha sido razonablemente sencilla; al menos si asumimos que, a menudo, el rango de IPs con acceso a esos puertos restringidos era conocido (oficinas, etc).

Con el tiempo, sobre los firewalls y todas sus variantes se han añadido otra serie de medidas "complementarias", que muchos no considerarán parte de la seguridad informática "de verdad", pero que han demostrado ser útiles si se usan adecuadamente y como parte de una solución más global. Me refiero, por ejemplo, al uso de librerías como TCPWrappers o de geolocalización, a los propios mecanismos de cada aplicación, o al uso de puertos no estándar para los servicios (los sandboxes por aplicación basados en la virtualización, Selinux y todo este tipo de medidas quedan fuera de este post).

Sin embargo, en la actualidad nos encontramos ante un problema añadido que no hemos tenido hasta la fecha: Las IPs origen que se tienen que conectar a esos servicios restringidos ya no son "tan estáticas" como antes. ¿Cómo abro el acceso SSH a un móvil? ¿Y el webmail corporativo? ¿Y el acceso IMAP?

Muchos diréis que nada como una buena VPN para solucionar este problemilla; y tendréis razón, claro. Ahora bien, el mundo de las redes privadas, por si sólo, tiene otra serie de problemas que no vamos a tratar aquí: ¿Qué tecnología VPN usamos? ¿Qué aplicación cliente? ¿A qué IPs permitimos establecer la conexión? ¿Dónde terminamos la red? ¿Qué acceso tiene un usuario de VPN una vez ha pasado ese terminador? En fin, lo dicho, todo un mundo.

En este post casi voy a limitarme a citar una herramienta más que usar a la hora de securizar un servidor: El Port Knocking. Ojo, se trata de un mecanismo adicional, y no de la solución definitiva a los problemas; pero sí es cierto que viene a ayudar con el problema del dinamismo actual de los orígenes.

El concepto general es realmente sencillo. El firewall del servidor mantiene bloqueado el puerto al que se quiere acceder, y sólo se habilita a través del envío de una secuencia determinada de paquetes. Las opciones son múltiples, y van desde simples SYN, en orden, a n puertos, hasta combinaciones más elaboradas, en las que se activan otros flags en las cabeceras.

A partir de esta idea básica, han ido apareciendo otras mejoras que vamos a ver en un minuto.

Port Knocking básico

La versión más sencilla del "protocolo" se basa, como vengo diciendo, en mandar una secuencia concreta de paquetes a varios puertos. Para su implementación en el lado del servidor, tenemos tres opciones. La primera requiere instalar software (knockd por ejemplo), y las otras dos usan únicamente iptables.

Si optáis por la vía de knockd, os tendréis que descargar el software (obviamente). Según la distribución que uséis, esto será más o menos fácil, así que no me voy a meter con la instalación.

[options]
  logfile = /var/log/knockd.log

[IMAPon]
  sequence    = 6030,6026,6031
  seq_timeout = 5
  command     = /sbin/iptables -I INPUT 2 -s %IP% -p tcp --dport 993 -j ACCEPT
  tcpflags    = syn

[IMAPoff]
  sequence    = 6040,6036,6041
  seq_timeout = 5
  command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 993 -j ACCEPT
  tcpflags    = syn

Esta es una configuración tipo, en mi caso de "/etc/knockd.conf". Como podéis suponer, cuando alguien envíe tres paquetes SYN a los puertos 6030,6026 y 6031, en ese orden, se ejecutará el comando definido en "command". En este ejemplo, es una simple regla iptables que permite que desde la IP origen se pueda conectar al puerto IMAP (IMAPon). Como la aplicación da la opción de lanzar más secuencias, se puede crear otra para eliminar la regla (IMAPoff).

Y poco más. En vuestro caso, tendréis que adaptar la regla iptables a vuestra configuración, o incluso podríais lanzar scripts más complejos, que por ejemplo manden un correo o alerta cada vez que se active el acceso.

Esta es la forma más simple de implementar el Port Knocking. Tiene fallos, como por ejemplo que un cambio de IP en el móvil supondría que la IP antigua tendría acceso permanente (o hasta eliminarla a mano), pero creo que son fáciles de solucionar (el match "recent" de iptables por ejemplo ofrece alternativas). Vosotros deberéis decidir si esto os sirve, o si necesitáis algo más elaborado.

La segunda forma de implementar esta versión original de Port Knocking es a través de iptables, sin software adicional. El proceso está muy bien documentado en el siempre magnífico wiki de archlinux, así que podéis seguir desde allí si optáis por esta vía.

Y para terminar, si queréis una alternativa específica de iptables, hay un módulo en xtables addons pensado para hacer Port Knocking. Se llama xt_pknock, y permite hacer cosas como esta (entre otras que veremos más adelante):

  iptables -A INPUT -p tcp -m pknock --knockports 4002,4001,4004 --strict --name IMAP --time 10 --autoclose 60 --dport 993 -j ACCEPT

El problema es que, hasta la fecha, os va a costar encontrar un kernel que traiga el módulo compilado, así que lo tendríais que hacer vosotros.

Port Knocking con autenticación

Aunque obligar a que el origen conozca la secuencia concreta que enviar al servidor sea útil, no es menos cierto que tiene margen de mejora. La más obvia va en el sentido de verificar que la conexión procede realmente desde un usuario autenticado.

Sobre esta idea, Michael Rash (autor, entre otros, de psad) implementó una mejora del Port Knocking sobre el concepto de Single Packet Authorization (SPA): fwknop. El objetivo es el mismo (abrir un puerto a través de iptables si usamos Linux), pero usando para ello un único paquete UDP con unos datos determinados. De esta manera, se evitan los problemas generados a partir del envío de múltiples paquetes (llegar desordenados, bloqueo por IDS, ...) y, además, da la opción de cifrar el payload con un algoritmo que también ofrezca autenticación. Podéis ver el listado de features y las ventajas de esta implementación en la propia web de fwknop.

La instalación de fwknopd es muy sencilla. De hecho, está disponible en muchas distribuciones. Como no podía ser de otra forma, tener muchas más funcionalidades también hace que la configuración sea algo más complicada que en el caso de knockd, aunque sigue siendo manejable.

Este es el momento en el que debería escribir algunas notas y ejemplos de configuración pero, la verdad, visto que en la web ya hay un buen tutorial, prefiero no alargar mucho más el post. Si tenéis alguna duda, escribid un comentario e intentaré resolverla. Tened en cuenta que fwknop es un proyecto "vivo", y que por lo tanto va mejorando con cada release. Las últimas versiones (a partir de la 2.5), por ejemplo, incluyen soporte para HMAC + SHA, de tal manera que se puede combinar con AES o GnuPG para mejorar la autenticación. Yo personalmente no he usado esta versión, así que no puedo comentar nada sobre esta nueva funcionalidad.

Una instalación tipo de fwknop, al menos en versiones anteriores a a la 2.5, usa dos ficheros de configuración. Cada parámetro está muy bien documentado, así que lo mejor es ir siguiendo los comentarios que veréis en fwknopd.conf y en access.conf. El primero se usa para definir si queremos poner el interfaz en modo promiscuo, el puerto en el que escucharemos los paquetes y, sobre todo, las cadenas de iptables que usaremos para incluir las reglas. Access.conf se usa para la parte más directamente relacionada con el acceso; empezando por todo lo relacionado con las claves de cifrado, y siguiendo con el contenido que puede ir en cada paquete, desde usuarios autorizados a puertos para los que se puede pedir acceso, pasando por un mecanismo de control de la IP origen desde la que se genera la solicitud.

Por último, y dejando a un lado fwknop, el módulo de Netfilter del que os he hablado antes, xt_pknock, también ofrece una versión de Port Knocking que ofrece SPA, de tal manera que se pueden escribir cosas como estas:

  ...
  iptables -A INPUT -p udp -m state --state NEW -m pknock --knockports 2000 --name IMAP --opensecret your_opensecret --closesecret your_closesecret -j DROP
  iptables -A INPUT -p tcp -m state --state NEW -m pknock --checkip --name IMAP -m tcp --dport 143 -j ACCEPT
  ...

Aún así, como os he dicho, este módulo todavía no es "demasiado fácil" de usar y, en todo caso, es más simple que lo que ofrece fwknop.

Clientes

La pregunta es: ¿Cómo se genera la secuencia que abre la puerta?

Si usamos la versión básica de Port Knocking, no hay ningún problema. Podemos usar la aplicación cliente del software (knock), o podemos usar nmap, nping, netcat, o cualquier otra aplicación que permita mandar paquetes con el flag SYN activo a un puerto concreto. Para móviles, también hay variedad; en android, por ejemplo, una búsqueda de "port knocking" da al menos dos aplicaciones gratuitas (y que funcionan, al menos en mi teléfono y tablet).

Si optamos por la versión con SPA, también tenemos aplicaciones para todo tipo de dispositivos y clientes, aunque en este caso tendremos que tirar, probablemente, por las aplicaciones creadas específicamente para fwknop. Yo personalmente no he probado las versiones para móvil, así que poco puedo aportar. En el tutorial tenéis los enlaces y sus limitaciones (sobre todo relacionadas con el uso de HMAC). Por supuesto, si estáis en Linux, no tendréis problema para usar el propio software cliente que trae fwknop.

Notas

No pretendo empezar una discusión sobre si el Port Knocking es útil o no, o de si entra dentro de lo llamado "Security through obscurity"; pero sí tengo claro que es una herramienta más, y que es perfectamente "usable" en muchos entornos. Ahora bien, aunque podamos estar de acuerdo en que el Port Knocking básico es algo limitado, la implementación de fwknop sí que es, indudablemente, mucho más completa desde el punto de vista de la seguridad informática.

read more

Monitorización orientada a host con OSSEC III

dom, 16 nov 2008 by Foron

En este último post de la serie vamos a ver, como siempre muy por encima, la funcionalidad "active response" que ofrece ossec. En definitiva, se trata de ser capaces de ejecutar un script cuando se activa un evento. Esto normalmente se usa para añadir reglas en firewalls, para añadir reglas tcp wrapper o para bloquear usuarios, pero en el fondo se puede hacer cualquier cosa que se pueda escribir en un programa. Por ejemplo, si una de las máquinas monitorizadas es un servidor de correo se podría añadir una IP que generase una alerta a una lista de acceso, o a una base de datos RBL. O también se podrían añadir reglas de modsecurity en un servidor web.

Para el ejemplo de este post, digamos que como administradores ya estamos usando psad para crear reglas en nuestro firewall. Digamos, además, que sólo queremos que sea psad el responsable de las reglas dinámicas en el cortafuegos.

Infraestructura ossec

Rootkits, accesos no permitidos, .... No parece necesario justificar la necesidad de tener el firewall monitorizado, ¿Verdad?

Nuestro objetivo es crear reglas para psad desde ossec. Primero definimos el comando en ossec.conf para poder usarlo más adelante en la configuración de la respuesta activa.

  <command>
    <name>psad</name>
    <executable>psad.sh</executable>
    <expect>user,srcip</expect>
  </command>

El script "psad.sh" (que guardaremos en $ossec_instalacion/active-response/bin con permisos de ejecución) tiene el siguiente contenido:

  #!/bin/bash
  # Anyade una regla de psad usando la IP origen. Tambien pasamos el usuario, pero no lo vamos a usar.
  # Entrada: user, srcip
  # Salida: Nada. Ejecuta una regla psad
  ACCION=$1
  USUARIO=$2
  IP=$3

  psad -fw-block-ip $IP

Ahora sólo queda configurar la respuesta activa en ossec.conf.

  <active-response>
    <disabled>no</disabled>
    <command>psad</command>
    <location>defined-agent</location>
    <agent_id>001</agent_id>
    <level>10</level>
    <rules_group>authentication_failures</rules_group>
  </active-response>

Estamos diciendo que vamos a ejecutar en el agente 001 (el firewall) el comando psad cuando ocurra un evento de nivel 10 y del grupo authentication_failures (por ejemplo la regla 5720).

Veamos si funciona bien intentando loguearnos varias veces desde una máquina (después de haber recargado la configuración de ossec). Este es el resultado

  Nov  9 00:50:03 firewall psad: added iptables auto-block against 192.168.10.133 for 3600 seconds

  Chain PSAD_BLOCK (1 references)
  target     prot opt source               destination
  DROP       all  --  192.168.10.133       0.0.0.0/0

Como siempre, y para aquellos que quieran profundizar más, recomiendo el libro http://www.elsevierdirect.com/product.jsp?isbn=9781597492409.

read more

Monitorización orientada a host con OSSEC II

jue, 13 nov 2008 by Foron

En mi primer post sobre ossec he mostrado los pasos para hacer una instalación básica y completamente estándar de ossec. En este post vamos a ver lo que hay por debajo del software.

Ossec usa tres componentes: un detector de rootkits, una herramienta para revisar la integridad del sistema, y el analizador de logs. Todas ellas se configuran en el fichero ossec.conf.

Este fichero de configuración tiene una estructura muy clara, en formato xml, en el que se definen varios bloques.

En este post sólo voy a dar algunas ideas básicas sobre el formato con el que se definen las reglas que describen los eventos detectables en los logs.

Tanto el chequeo de integridad como el detector de rootkits se configuran de una forma muy similar, así que lo dejaré para que quien quiera se pelee con ello.

En ossec.conf se definen los logs que se quieren monitorizar dentro de secciones "localfile". Un ejemplo:

  <localfile>
    <log_format>syslog</log_format>
    <location>/var/log/auth.log</location>
  </localfile>

En este caso se dice que queremos vigilar auth.log, del tipo syslog. Existen varios formatos definidos, como apache o snort-full, por citar dos ejemplos. Syslog se usa en los casos en los que se loguea un único evento por linea. Sort-full, por poner otro ejemplo, está adaptado al tipo de log que deja snort cuando usa el formato de salida full.

Una vez definidos los formatos, empezamos a hablar de reglas. El primer paso es que ossec detecte qué tipo de entrada de log es. Para esto se usan los decoders (decoder.xml) Veamos un ejemplo.

  <decoder name="sshd">
  <program_name>^sshd</program_name>
  </decoder>

Este decoder "se activa" cuando la entrada de log es generada por el programa sshd (sshd[5493] en el siguiente ejemplo):

  Feb  3 19:22:33 server sshd[5493]: Accepted publickey for prueba from 192.168.10.2 port 50560 ssh2

Pero, a pesar de la importancia de esta sencilla regla (después veremos por qué), necesitamos extender el decoder para que sea útil.

  <decoder name="ssh-failed">
  <parent>sshd</parent>
  <prematch>^Failed \S+ </prematch>
    <regex offset="after_prematch">^for (\S+) from (\S+) port \d+ \w+$</regex>
    <order>user, srcip</order>
  </decoder>

Gracias a la primera y simple regla "sshd" nos aseguramos que ossec sólo va a analizar la entrada de log contra esta segunda regla, mucho más compleja, si dicho log está relacionado con ssh. Esto es una gran mejora en el rendimiento del sistema.

Sin entrar en explicaciones detalladas, esta regla se cumple con este tipo de log:

  Jul 26 22:06:10 server sshd[3727]: Failed password for prueba from 192.168.10.2 port 50519 ssh2

Y además crea dos "variables"; una con el usuario que ha intentado conectarse y otra con la IP origen.

Teniendo estos datos ya podemos empezar con las reglas "de verdad". Veamos algo del fichero sshd_rules.xml

  <rule id="5700" level="0" noalert="1">
    <decoded_as>sshd</decoded_as>
    <description>SSHD messages grouped.</description>
  </rule>

  <rule id="5716" level="5">
    <if_sid>5700</if_sid>
    <match>^Failed|^error: PAM: Authentication</match>
    <description>SSHD authentication failed.</description>
    <group>authentication_failed,</group>
  </rule>

De manera similar a los decoders, se define una primera regla básica para ssh, que ayudará a ossec en no tener que analizar reglas de apache (por ejmplo) en logs de ssh.

La segunda regla (5716) generará una alerta de nivel 5 cuando haya un intento de login erroneo. Pero también se pueden crear reglas compuestas que activarán alertas de mayor nivel si hay x intentos fallidos desde una misma IP

  <rule id="5720" level="10" frequency="6">
    <if_matched_sid>5716</if_matched_sid>
    <same_source_ip />
    <description>Multiple SSHD authentication failures.</description>
    <group>authentication_failures,</group>
  </rule>

Gracias al formato xml es muy sencillo añadir nuevas reglas. También se pueden enviar correos con las alertas a cuentas de correo diferentes en base, por ejemplo, al agente que ha generado la alerta o al nivel de la misma.

En el tercer y último post de la serie vamos a integrar ossec con psad.

read more

Inspección de tráfico con tcpdump y tcpflow

sáb, 08 nov 2008 by Foron

Antes de seguir con el segundo post sobre ossec, voy a dar un par de pistas sobre cómo ver el tráfico que pasa por una sesión tcp. Esto sí que es todo un mundo, así que me limito, como casi siempre, a dar cuatro detalles para que quien quiera se haga una idea de lo que se puede hacer y siga investigando.

Por supuesto, el que se pueda "espiar" lo que se está trasmitiendo no significa que debamos (ni podamos) hacerlo, al menos si no queremos tener problemas legales.

Vamos a empezar por lo básico. Necesitamos capturar tráfico para poderlo analizar después con cierta tranquilidad. Para esto usamos el conocido tcpdump, aunque podemos usar otros, como por ejemplo, snort.

  tcpdump -n -i eth1 -s 1515 -U -w /tmp/captura.pcap '(tcp port 20) or (tcp port 21) or (tcp port 25)'
  tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 1515 bytes

Para saber lo que hacen los parámetros nada mejor que el manual :-)

Dejamos esta terminal abierta y con el comando en ejecución. Vamos a ir analizando lo que se vuelca en captura.pcap en otra terminal.

Primera prueba.

Desde otra máquina vamos a hacer un telnet al puerto 25 y a mandar un correo.

  telnet 192.168.10.1 25
  Trying 192.168.10.1...
  Connected to 192.168.10.1.
  Escape character is '^]'.
  220 smtp.example.com ESMTP Postfix
  helo prueba.example.com
  250 smtp.example.com
  rset
  250 2.0.0 Ok
  helo prueba.example.com
  250 smtp.example.com
  mail from: prueba@example.com
  250 2.1.0 Ok
  rcpt to: prueba1@example.com
  250 2.1.5 Ok
  data
  354 End data with .
  Subject: Titulo del correo
  Este texto se ve en la captura
  .
  250 2.0.0 Ok: queued as 1F7426C420
  quit
  221 2.0.0 Bye
  Connection closed by foreign host.

Nuestro volcado tiene datos.... vamos a ver que tiene usando tcpflow.

  # tcpflow -r captura.pcap -c port 25
  192.168.010.001.00025-192.168.010.002.41403: 220 smtp.example.com ESMTP Postfix
  192.168.010.002.41403-192.168.010.001.00025: helo prueba.example.com
  192.168.010.001.00025-192.168.010.002.41403: 250 smtp.example.com
  192.168.010.002.41403-192.168.010.001.00025: rset
  192.168.010.001.00025-192.168.010.002.41403: 250 2.0.0 Ok
  192.168.010.002.41403-192.168.010.001.00025: helo prueba.example.com
  192.168.010.001.00025-192.168.010.002.41403: 250 smtp.example.com
  192.168.010.002.41403-192.168.010.001.00025: mail from: prueba@example.com
  192.168.010.001.00025-192.168.010.002.41403: 250 2.1.0 Ok
  192.168.010.002.41403-192.168.010.001.00025: rcpt to: prueba1@example.com
  192.168.010.001.00025-192.168.010.002.41403: 250 2.1.5 Ok
  192.168.010.002.41403-192.168.010.001.00025: data
  192.168.010.001.00025-192.168.010.002.41403: 354 End data with .
  192.168.010.002.41403-192.168.010.001.00025: Subject: Titulo del correo
  192.168.010.002.41403-192.168.010.001.00025: Este texto se ve en la captura
  192.168.010.002.41403-192.168.010.001.00025: .
  192.168.010.001.00025-192.168.010.002.41403: 250 2.0.0 Ok: queued as 1F7426C420
  192.168.010.002.41403-192.168.010.001.00025: quit
  192.168.010.001.00025-192.168.010.002.41403: 221 2.0.0 Bye

Sorpresa, tcpflow ha generado, en esta caso por salida estándar (-c), todo lo que ha sido capturado en el puerto 25.

Segunda prueba.

Bien, vamos con algo un poco diferente, pero igual de fácil (recordad que esto no son más que ideas)

Ahora vamos a suponer que he programado un rootkit que se llama exploit, y que lo voy a subir por ftp a la máquina que estamos monitorizando.

  ftp 192.168.10.1
  Connected to 192.168.10.1.
  220 Este es un servidor privado. Por favor cierre la sesion inmediatamente.
  Name (192.168.10.1:prueba):
  331 Please specify the password.
  Password:
  230 Login successful.
  Remote system type is UNIX.
  Using binary mode to transfer files.
  ftp> put exploit
  local: exploit remote: exploit
  200 PORT command successful. Consider using PASV.
  150 Ok to send data.
  226 File receive OK.
  101992 bytes sent in 0.00 secs (29433.1 kB/s)
  ftp> quit
  221 Goodbye.

Muy bien, ahora veamos lo que tenemos, empezando por el puerto 21, y después por el 20.

  # tcpflow -r captura.pcap -c port 21
  192.168.010.001.00021-192.168.010.002.50427: 220 Este es un servidor privado. Por favor cierre la sesion inmediatamente.
  192.168.010.002.50427-192.168.010.001.00021: USER prueba
  192.168.010.001.00021-192.168.010.002.50427: 331 Please specify the password.
  192.168.010.002.50427-192.168.010.001.00021: PASS secreto
  192.168.010.001.00021-192.168.010.002.50427: 230 Login successful.
  192.168.010.002.50427-192.168.010.001.00021: SYST
  192.168.010.001.00021-192.168.010.002.50427: 215 UNIX Type: L8
  192.168.010.002.50427-192.168.010.001.00021: TYPE I
  192.168.010.001.00021-192.168.010.002.50427: 200 Switching to Binary mode.
  192.168.010.002.50427-192.168.010.001.00021: PORT 192,168,10,2,205,32
  192.168.010.001.00021-192.168.010.002.50427: 200 PORT command successful. Consider using PASV.
  192.168.010.002.50427-192.168.010.001.00021: STOR exploit
  192.168.010.001.00021-192.168.010.002.50427: 150 Ok to send data.
  192.168.010.001.00021-192.168.010.002.50427: 226 File receive OK.
  192.168.010.002.50427-192.168.010.001.00021: QUIT
  192.168.010.001.00021-192.168.010.002.50427: 221 Goodbye.

Vamos a hacer ahora que tcpflow genere un fichero con el tráfico del puerto 20. Para esto quitamos el parámetro -c.

  # tcpflow -r captura.pcap  port 20
  # file 192.168.010.002.52512-192.168.010.001.00020
  192.168.010.002.52512-192.168.010.001.00020: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, stripped

Sorpresa, tenemos un fichero binario ..... con el exploit. Bueno, en realidad en una copia de /bin/ls, pero vale para el ejemplo

  # strings 192.168.010.002.52512-192.168.010.001.00020
  .......
  Usage: %s [OPTION]... [FILE]...
  List information about the FILEs (the current directory by default).
  Sort entries alphabetically if none of -cftuvSUX nor --sort.
  Mandatory arguments to long options are mandatory for short options too.
    -a, --all                  do not ignore entries starting with .
    -A, --almost-all           do not list implied . and ..
        --author               with -l, print the author of each file
    -b, --escape               print octal escapes for nongraphic characters
        --block-size=SIZE      use SIZE-byte blocks
  .......

¿Qué pasa en la realidad, cuando tenemos cientos o miles de sesiones de correo o ftp y queremos ver una concreta? Por un lado debemos guardar el tráfico que queremos investigar, claro, pero luego debemos saber qué sesiones se han establecido, a qué hora, cuánto tráfico ha pasado por ellas, entre que puertos, .... Para esto hay mucho software, pero un buen ejemplo es argus. A partir de aquí, podemos generar expresiones más elaboradas para usar en tcpflow (algo más que "port 20")

read more