Bro IDS tips and tricks

Como continuación del excelente post sobre Bro que publicó nuestro compañero Juan Manuel hace un tiempo, en el presente recopilamos diversos tips and tricks para dicha herramienta que esperamos que os sean de utilidad. Nos centraremos en la última versión estable, Bro 2.5, publicada en noviembre del pasado año 2016.

Captura sobre varios interfaces

En algunas ocasiones, el tráfico a monitorizar nos llega por varios interfaces de red diferentes; por ejemplo, en el caso de disponer de dos salidas a Internet.

Una forma (un tanto hacky) de conseguir que Bro escuche sobre dos o más interfaces es indicarlo del siguiente modo en el fichero node.cfg, dentro de la sección [bro], correspondiente a una configuración standalone (i.e. un sólo nodo se ocupará de todo el procesamiento del tráfico de red):

[bro]
type=worker
host=localhost
interface='eth0 -i eth1'

En este caso, el ‘eth0 -i eth1’ (ojo con las comillas simples: son necesarias para que se interpole bien la variable) tendría el efecto de que Bro escucharía sobre ambos interfaces, siendo invocado por broctl con la siguiente línea de comandos:

/usr/local/bro/bin/bro -i eth0 -i eth1 \
  -U .status -p broctl -p broctl-live \
  -p standalone -p local -p bro local.bro \
  broctl broctl/standalone broctl/auto

Otra opción sería definir dos (o más) workers (procesos) independientes, uno para cada interfaz de red. En este caso, pasamos de una configuración standalone a una en cluster (profundizamos más sobre los clusters en la sección sobre PF_RING de más adelante):

# Comentamos la configuración para modo standalone
#
#[bro]
#type=standalone
#host=localhost
#interface='eth0 -i lo'

# Definimos un manager y un proxy, necesarios
# para sincronizar el trabajo de los workers
#
[manager]
type=manager
host=localhost

[proxy-1]
type=proxy
host=localhost

# Definimos un worker para eth0
[worker-1]
type=worker
host=localhost
interface=eth0

# Definimos un worker para eth1
[worker-2]
type=worker
host=localhost
interface=eth1

En este caso, broctl levanta un proceso por cada interfaz, con las siguientes líneas de comandos:

# worker-1 eth0
/usr/local/bro/bin/bro -i eth0 \
  -U .status -p broctl -p broctl-live \
  -p local -p worker-1 local.bro broctl \
  base/frameworks/cluster local-worker.bro broctl/auto

# worker-2 eth1
/usr/local/bro/bin/bro -i eth1 \
  -U .status -p broctl -p broctl-live \
  -p local -p worker-2 local.bro broctl \
  base/frameworks/cluster local-worker.bro broctl/auto

Con la primera opción, el mismo proceso worker se encargaría de ambos interfaces, mientras que con la segunda utilizaríamos dos procesos. La mejor opción dependerá de los recursos de la máquina que ejecuta Bro, el caudal de red a procesar, etc. lo que nos lleva al siguiente punto.

Monitorizando la pérdida de paquetes

En cualquier IDS o analizador de red, es fundamental medir la pérdida de paquetes que estamos sufriendo, con el objetivo de dimensionar adecuadamente nuestros sistemas. Esta información nos la proporciona el subcomando netstats de broctl:

[BroControl] > netstats
   worker-1: 1484003365.475952 recvd=1947 dropped=0 link=1947
   worker-2: 1484003365.675937 recvd=751 dropped=0 link=1608

En nuestro laboratorio de prueba, con los dos workers (uno por interfaz), no tendríamos pérdidas (dropped=0). Si, como es habitual en un entorno real, tenemos pérdidas, podemos intentar disminuirlas de alguna de las siguientes formas.

Parametrizando la captura

En ocasiones (por ejemplo, si tenemos pérdida de paquetes o sólo queremos procesar ciertas subredes o tipos de tráfico) puede interesarnos que Bro no analice todo lo que recibe por las interfaces de captura configuradas, sino tan sólo un subconjunto del tráfico.

Para conseguir esto ya hemos de empezar a hablar del lenguaje de scripting propio de Bro, pues es necesario redefinir la variable capture_filters.

El fichero donde suele incluirse este tipo de redefiniciones, parametrizaciones y código de scripting es local.bro (/usr/local/bro/share/bro/site/local.bro). En este caso, la variable capture_filters sería de tipo table (lo que vendría a ser un dict en Python; una tabla asociativa, hash o “diccionario”), asociando claves con valores.

Por ejemplo, para capturar sólo el tráfico relacionado con los puertos 21 y 25 TCP, especificaríamos:

redef capture_filters += {
    ["smtp"] = "port 25",
    ["ftp"] = "port 21"
};

La clave smtp (un nombre arbitrario, a nuestra elección, con suerte descriptivo de lo que viene a continuación ;-) se asociaría con el filtro BPF que Bro tiene que aplicar al tráfico: port 25. El filtro BPF final, teniendo en cuenta los dos elementos de la tabla, vendría a ser port 25 or port 21, aunque realmente ya se encarga Bro de construirlo según considere, añadiendo filtros para VLAN, “distribuyendo” el filtro a todos los workers y demás elementos necesarios, etc. El resto del tráfico sería descartado.

Para validar la configuración, instalarla y reiniciar Bro, podemos utilizar el subcomando deploy de broctl:

$ sudo /usr/local/bro/bin/broctl deploy
checking configurations ...
installing ...
removing old policies in /usr/local/bro/spool/installed-scripts-do-not-touch/site ...
removing old policies in /usr/local/bro/spool/installed-scripts-do-not-touch/auto ...
creating policy directories ...
installing site policies ...
generating cluster-layout.bro ...
generating local-networks.bro ...
generating broctl-config.bro ...
generating broctl-config.sh ...
stopping ...
stopping worker-1 ...
stopping worker-2 ...
stopping proxy-1 ...
stopping manager ...
starting ...
starting manager ...
starting proxy-1 ...
starting worker-1 ...
starting worker-2 ...

Si por ejemplo sólo nos interesara la subred 192.168.12.0/24, podríamos redefinir la variable de esta forma:

redef capture_filters += {
    ["servidores"] = "net 192.168.12.0/24"
};

Bro diseccionaria todos los tipos de tráfico (HTTP, SMTP, etc.), pero sólo los de esa subred.

Instalación con PF_RING

Al igual que otras plataformas como Snort, Bro es un sistema monohilo, por lo que en principio todo el tráfico sería procesado por un solo proceso (en consecuencia, por un solo core del procesador) del sistema operativo.

No obstante, como se ha introducido antes en lo referido al sniffing sobre varios interfaces, los creadores de Bro lo diseñaron con una arquitectura en cluster en mente, siendo capaz de coordinar varios workers, cada uno corriendo en diferentes cores de la misma máquina o incluso en máquinas diferentes, posibilitando escalar la solución ante grandes volúmenes de tráfico a procesar.

Una de las formas más utilizadas de distribuir el tráfico ente múltiples workers al mismo tiempo que se mejora la velocidad de adquisición (aunque hay otras, como netmap), al igual que en el
caso de Snort y otros IDS como Suricata, es utilizando PF_RING.

A grandes rasgos, lo que hace Bro es utilizar la capa de compatibilidad que tiene PF_RING con la API de libpcap. Incluimos a continuación instrucciones rápidas para la instalación de Bro con soporte para PF_RING (basadas en la documentación oficial, pero con algunos matices):

# Instalación de dependencias sobre un
# sistema Debian.
#
$ sudo apt-get install cmake make \
    gcc g++ flex bison libpcap-dev \
    libssl-dev python-dev swig zlib1g-dev

$ tar xvfz PF_RING-6.4.1.tar.gz

# Instalación de la librería en userland.
#
$ cd PF_RING-6.4.1/userland/lib
$ ./configure --prefix=/opt/pfring
$ sudo make install
$ ls /opt/pfring/lib/
libpfring.a  libpfring.so

# Instalación de la capa de compatibilidad
# con libpcap.
#
$ cd ../libpcap
$ ./configure --prefix=/opt/pfring
$ sudo make install
$ ls /opt/pfring/lib/
libpcap.a  libpcap.so  libpcap.so.1  libpcap.so.1.7.4  libpfring.a  libpfring.so

# Instalamos también un tcpdump con soporte
# para PF_RING.
#
$ cd ../tcpdump-4.7.4/
$ ./configure --prefix=/opt/pfring
$ sudo make install
$ ls /opt/pfring/sbin/
tcpdump  tcpdump.4.7.4

# Instalamos y cargamos el módulo del kernel
# (consultar la doc de PF_RING para más opciones).
#
$ cd ../../kernel/
# OJO, hay que hacer el "make" primero, en contra de lo que
# indica la documentación oficial; un solo "make install" no
# contruye el módulo para el kernel.
$ make
$ sudo make install
$ sudo modprobe pf_ring enable_tx_capture=0 min_num_slots=32768
$ lsmod | grep pf_ring
pf_ring               712704  0 

# Por fin, instalamos Bro, utilizando la
# libpcap de PF_RING.
#
$ tar xvfz bro-2.5.tar.gz
$ cd bro-2.5/
$ ./configure --with-pcap=/opt/pfring 
$ make
$ sudo make install

# Comprobamos que la libpcap que utiliza
# el binario de Bro es la de PF_RING.
#
$ ldd /usr/local/bro/bin/bro | grep pcap
    libpcap.so.1 => /opt/pfring/lib/libpcap.so.1 (0x00007facb9375000)

Aún se puede sacar más partido de PF_RING utilizando funcionalidades como Zero Copy, o configurando adecuadamente los diferentes parámetros de la tarjeta de red, el módulo del kernel, etc.
Para más información se puede consultar la documentación oficial de ambos proyectos.

Una vez compilado y funcionando Bro con soporte para PF_RING, broctl facilita enormemente su configuración. Bastaría con configurar un worker en el fichero node.cfg de un modo similar al siguiente:

[worker-1]
type=worker
host=localhost
interface=eth0
lb_method=pf_ring
lb_procs=10
pin_cpus=2,3,4,5,6,7,8,9,10,11

Con la anterior configuración, Bro lanzará 10 procesos (lb_procs=10), cada uno “fijado” (CPU pinning) a un core (pin_cpus=2,3,4,5,6,7,8,9,10,11; el primero al core 2, el segundo al 3, etc.),
con el método de balanceo PF_RING (lb_method=pf_ring). El tráfico se adquirirá de la interfaz eth0, repartiéndolo entre todos los procesos. Como se puede ver, en esencia lo que se consigue es algo similar a las típicas configuraciones multiproceso de IDS como Snort, pero el propio Bro dispone “de serie” del scripting necesario para prepararlo y lanzarlo todo.

Con este tipo de configuración seríamos capaces de exprimir al máximo la máquina donde se ejecuta Bro. Si aún así perdemos tráfico de red, y no podemos (o queremos) limitar el tráfico a procesar con capture filters, tendríamos que pasar a una configuración en cluster formada por varios servidores.

Extracción de ficheros

La filosofía de funcionamiento de Bro se basa en que todo el tráfico que se va capturando pasa por los analizadores de protocolo adecuados, que diseccionan los protocolos y “disparan” eventos.
Por ejemplo, cuando el analizador del protocolo TCP observa el establecimiento de una conexión TCP, lanza el correspondiente evento; cuando el analizador del protocolo HTTP observa una respuesta determinada a una petición GET desde un servidor hacia el cliente de la sesión HTTP correspondiente, lanza otro disparo.

Este sería el core de Bro; la mayor parte del resto del trabajo (emitir una alerta, generar ficheros de registro, como los que se van depositando en /usr/local/bro/logs, etc.) se deja para el motor de scripting, que como se ha mencionado consiste en un lenguaje interpretado propio.

Es claro por lo tanto que las capacidades de parametrización de la herramienta son enormes, y podemos encontrar scripts escritos por terceros “casi para cualquier cosa”. Como ejemplo útil de uso del lenguaje de scripting os mostraremos un script homegrown que consideramos de utilidad: dado un conjunto de direcciones de email “sensibles” de la organización, programaremos Bro para que guarde todos los ficheros adjuntos que el analizador del protocolo SMTP vaya detectando.

El primer paso será definir qué buzones de correo “nos interesan”. Esto lo haremos con una variable global (modificador glob) de tipo expresión regular (o pattern, en la terminología de Bro, /regex/) en la que especificaremos las direcciones:

global addrs =
    /un_usuario@foo.com/
  | /otro_usuario@bar.com/
;

Esto hará match con la cadena un_usuario@foo.com o con la cadena otro_usuario@bar.com. A continuación, hookeamos el evento file_new, que es el que se dispara cuando cualquier analizador de protocolo procesa un nuevo fichero. Este evento forma parte del File Analysis Framework de Bro (los frameworks son conjuntos de funcionalidades similares cuyo código fuente viene agrupado), y su prototipo puede consultarse en la documentación oficial. El código sería el siguiente:

# Ejemplo de extracción de ficheros adjuntos SMTP.
# Código muy mejorable ;-)
#
event file_new(f: fa_file)
{

 local addp = F;
 for (cid in f$conns) {
   if (f$conns[cid]$smtp?$to) {
     for (to in f$conns[cid]$smtp$to) {
       if (addrs in to) {
         addp = T;
       }
     }
   }

   if (!addp) {
     if (f$conns[cid]$smtp?$rcptto) {
       for (to in f$conns[cid]$smtp$rcptto) {
         if (addrs in to) {
           addp = T;
         }
       }
     }
   }
 }

 if (addp) {
   local fname = fmt("/usr/local/bro/files/%s-%s", f$source, f$id);
   Files::add_analyzer(f, Files::ANALYZER_EXTRACT, [$extract_filename=fname]);
   #print fname;
 }
}

Básicamente, obviando la idiosincrasia del lenguaje de scripting de Bro (con sintaxis un tanto diferente a la de lenguajes como Python; por ejemplo, para el acceso a un campo de un registro se utiliza el símbolo $ en vez del habitual punto), lo que hacemos es consultar la conexión a la que pertenece el fichero (del tipo fa_file), y si esa conexión es de tipo SMTP comparamos el campo to con nuestra expresión regular, fijando la variable booleana addp a T (true, verdadero).

En el último if, si la variable es verdadera, definimos el lugar donde guardaremos el fichero (variable fname) y utilizamos la función add_analyzer del File Analysis Framework. Puesto que invocamos a la función con el parámetro ANALYZER_EXTRACT, el framework se ocupará de extraer el fichero en la ruta especificada (parámetro extract_filename.

El último print está comentado, y puede sernos muy útil cuando estamos depurando el script o comprobando que funciona correctamente. La salida se almacenará en el fichero stdout.log, dentro del directorio de registros de Bro.

Como se observa, no sería complicado realizar cualquier tipo de procesamiento que se nos ocurra: extraer ficheros transferidos por HTTP, además de los adjuntos de correo electrónico, pasar los ficheros por Yara, o extraer directamente información de las cabeceras o el cuerpo de los mensajes de correo electrónico (como ejemplo de esto último, tenemos el script smtp-url.bro, que almacena en un fichero de registro todas las URL observadas en los cuerpos de los mensajes de correo electrónico; muy útil si por ejemplo nos interesa ver cómo ha llegado un ransom o un phishing). Lo único necesario es comprender el lenguaje de scripting (documentación de referencia), y los diferentes frameworks y definiciones de scripts y datos disponibles.

Detección de IOC: Intelligence Framework

Otro componente de Bro extremadamente interesante es el Intelligence Framework.

A grandes rasgos, la idea es guardar nuestros IOC (en un formato específico) en un fichero, por ejemplo /usr/local/bro/etc/intel.txt:

#fields indicator       indicator_type  meta.source     meta.desc       meta.url
1.2.3.4 Intel::ADDR     source1 Sending phishing email  http://source1.com/badhosts/1.2.3.4
a.b.com Intel::DOMAIN   source2 Name used for data exfiltration -

y añadir las siguientes dos líneas al fichero local.bro:

redef Intel::read_files += { "/usr/local/bro/etc/intel.txt" };
@load policy/frameworks/intel/seen

Cualquier contacto con la dirección IP o dominio especificados será registrado en el fichero intel.log.
Se pueden introducir otros tipos de indicadores: subredes en formato CIDR, URL, User-Agent de navegadores, direcciones de correo electrónico, hashes SHA1 (que serán comparados con los descargados por HTTP, FTP, etc. y los que van adjuntos a mensajes de correo electrónico), nombres de fichero (de nuevo, presentes en sesiones HTTP, SMTP, etc.), entre otros. Existen incluso feeds de inteligencia que ofrecen sus IOC directamente en el formato de este framework para Bro.

Malware hash registry

Un último (y breve) tip: por defecto, todos los hashes de ficheros que Bro “ve” se envían al Malware Hash Registry de Team Cymru. Si este comportamiento no es el deseado, podemos desactivarlo comentando la siguiente línea de local.bro y recargando la configuración:

# Detect SHA1 sums in Team Cymru's Malware Hash Registry.
@load frameworks/files/detect-MHR

Conclusión

Esperamos que los anteriores tips and tricks os hayan sido útiles, y que puedan ahorraros el tiempo que hemos tenido que dedicar para conseguir que algunas cosas funcionaran ;-) Si considerais
que alguno de los puntos puede mejorarse (o contiene algún error), o teneis otros consejos que os gustaría compartir, no dudéis en hacérnoslos llegar.

Aprovechamos para incluir un enlace a las slides de la BroCon 2016. Si os ha gustado esta entrada, seguro que las encontrareis de vuestro interés:

https://www.bro.org/community/brocon2016.html

¡Hasta el próximo post! @pmarinram