pre { background:#eeeeee; border:1px solid #A6B0BF; font-size:120%; line-height:100%; overflow:auto; padding:10px; color:#000000 } pre:hover { border:1px solid #efefef; } code { font-size:120%; text-align:left; margin:0;padding:0; color: #000000;} .clear { clear:both; overflow:hidden; }

Bienvenido al blog

Bienvenidos al blog de seguridad en sistemas

lunes, 12 de diciembre de 2011

Presentando Mail Malware Trap

A raíz de la entrada del correo electrónico donde se enlazaba a un troyano bancario, comencé a desarrollar un programita que tenía en mente desde hacía tiempo. Se trata de un recolector de Malware de correos electrónicos, encargado de acceder a distintas cuentas de correo, obtener el correo electrónico, almacenarlos en base de datos, y en caso de poseer adjuntos, ser analizados con la API de Virus total.

Veámoslo con más detenimiento. Primero será necesario disponer de una serie de sondas, correspondientes a cuentas de correo electrónico perteneciente en distintos servidores públicos como son Gmail, Yahoo o Hotmail, así como una serie de correos privados como por ejemplo el corporativo. Dichas cuentas de correo solo se usarán para recoger el correo que llegue a la bandeja de entrada y Spam. Para ello en ocasiones será necesario hacer filtros para trasladar el correo de la carpeta Spam a bandeja de entrada. Por ejemplo para Gmail sería necesario crearse un filtro de tipo texto con la siguiente entrada “is:spam” e indicar que debe enviarse como correo no leído a la bandeja de entrada. Con esto dispondremos de varias cuentas de correo distribuidas sobre distintos servidores de correo cuyo único objetivo es almacenar los correos de entrada.


Así, el recolector de malware lo que hará será conectarse a las cuentas para descargarse el correo nuevo y una vez obtenido insertará en una base de datos MySQL el correo descargado, indicando de qué servidor de correo lo ha descargado, a qué hora ha llegado, quién lo ha enviado, con qué asunto y con qué texto. Tanto el asunto como el texto se almacenarán en base64 en la base de datos.

El agente también comprobará si el correo electrónico tiene adjuntos. En caso de tener se descargará el adjunto conservando su nombre y lo almacenará en un árbol de sistema de ficheros compuesto por año, mes y identificador de correo de la base de datos. A su vez se insertará en la base de datos de que correo procede el adjunto, donde está almacenado, su comprobación MD5, si ha sido subido anteriormente a Virus Total y en caso afirmativo, se nos mostrará el resultado de los antivirus.




Veamos como funciona. En primer lugar será necesario disponer de una base de datos MySQL en el servidor donde instalemos el agente. Una vez dispongamos del MySQL será necesario crearnos el usuario y la base de datos mediante las siguientes órdenes:

$ mysql -u root -p
> CREATE database mailmalwaretrap;
> CREATE USER 'mailmalwaretrap'@'localhost' IDENTIFIED BY 'MiContrasenya';
> GRANT SELECT, INSERT, DELETE ON mailmalwaretrap.* TO 'mailmalwaretrap'@'localhost';

A continuación volcaremos el esquema de la base de datos, que podéis obtener de mailMalwareTrap.sql (los ficheros están al final del post):

$ mysql -u root -p < mailMalwareTrap.sql

Posteriormente configuraremos el agente, escribiendo en el fichero “mailMalwareTrap.conf” una línea por cada cuenta de correo que dispongamos, teniendo en cuenta que se deberá seguir el siguiente esquema “servidorCorreo|cuentaCorreo|contraseña”. Si por ejemplo nuestro servidor de correo es “pop.gmail.com”, la cuenta “lol@gmail.com” y contraseña “lolazo” el fichero será el siguiente:

# cat mailMalwareTrap.conf
pop.gmail.com|lol@gmail.com|lolazo

Para finalizar obtendremos el agente “mailMalwareTrap.py” donde será necesario tener instalado las dependencias de MySQL para Python.

Para el ejemplo que mostraremos usaremos una única cuenta de Gmail donde enviaremos desde otro servidor de correo adjuntando un fichero pdf que corresponde con el meterpreter. Una vez enviado ejecutaremos el script para que procese los correos nuevos. Se aconseja que se ponga el script en el crontab para que se ejecute cada hora:

mailMalwareTrap.py
Hay correos nuevos en "pop.gmail.com".
$

Si accedemos a la base de datos tabla “mail”, veremos que se ha insertado el nuevo correo con identificador “1”, correspondiente a la sonda “1”, que ha llegado a las 10:52 del 11 de Noviembre, cuyo asunto y texto están en base64 para evitar “sorpresas”:


Si analizamos la tabla malware veremos lo siguiente:



Malware con identificador 1, procedente del correo con ID 1, que se encuentra almacenado en “/tmp/result/2011_11/1/meterpreter.pdf”, que ya había subido con anterioridad a Virus Total, listado de los resultados de los distintos antivirus (la mayoría lo detectan), texto añadido por nosotros (en este caso está a NULL) y para finalizar la comprobación md5 del malware.
Si comprobamos a mano la comprobación md5 veremos que es la misma que hay almacenada en la base de datos:

md5sum /tmp/result/2011_11/1/meterpreter.pdf
062e7ecdc4a15f2f49cb5b2b09e5a4ea /tmp/result/2011_11/1/meterpreter.pdf
$

Como ven es una forma sencilla de tener un recolector de malware de las carpetas Spam de distintos servidores que permite analizar los riesgos que pueden acontecer en su infraestructura.
Como mejoras pendientes tengo dos puntos claramente identificados: el primero será buscar en el texto del correo posibles URLs y analizar con Virus Total si se tratan de URL con malas intenciones. Así mismo el segundo punto lo constituye una interfaz gráfica que permita gestionar los correos de forma más sencilla. Pero todo esto ya otro día ;)

Espero que les haya gustado la entrada, todo sea dicho, imagino que alguien habrá tenido la idea antes, pero sinceramente, yo creo que para aprender es necesario hacerlo uno mismo. Los ficheros pueden descargarlos directamente desde este enlace: mailMalwareTrap.rar.

PD: tengo pendiente de subirlo al subversión con la nueva actualización pero por problemas de tiempo me ha sido imposible, intentare que la segunda versión esté para Febrero pero entre GREM y canción de hielo y fuego estoy ocupado.

PD2: entrada escrita también para el blog de mi empresa SecurityArtWork.


Continuar...

martes, 8 de noviembre de 2011

Un correo ¿amigo?, no, troyano bancario

Ayer domingo mientras pensaba qué excusa poner a mi compañero Raúl por lo del partido del Levante ante el Valencia —donde todo sea dicho de paso claramente nos robaron— me llegó un correo electrónico con una supuesta oferta de trabajo, tal como podemos ver en la siguiente imagen:


El enlace apuntaba a la siguiente dirección “http://anyhub.net/file/5r_v-oferta_838as32-pdf.exe”, donde como puede verse juega con la terminación “pdf” pero tiene extensión “exe”. Al analizar la cabecera del fichero vemos el “MZ” inicial típico de un ejecutable Windows; claramente todo apunta a Malware. Al buscar cadenas de texto en el binario mediante la herramienta “strings” vemos que el ejecutable contiene texto como el siguiente:

Microsoft Internet Security Banking Connections.

El c
digo de seguridad CVV/CVC, es un c
digo formado por tres cifras que est
situado en la parte trasera de su tarjeta de cr
dito.

A la vista de estos primeros indicios, lo que hice fue introducir el fichero en una máquina virtual VMWare para ver qué es lo que realmente hace. Lo primero que observamos es que al binario le han puesto el icono de un PDF de Acrobat Reader para hacer pasar al fichero por PDF a ojos de un usuario inexperto:


Al hacer click sobre el fichero no se ejecuta aparentemente nada, simulando de esta manera que el fichero no se ha abierto correctamente. No obstante, si analizamos los procesos en ejecución vemos que realmente sí que se está ejecutando el programa:


Como hemos visto con la orden strings, parece ser que se trata de un troyano con claros síntomas de intentar obtener datos bancarios, por lo que ejecutamos Internet Explorer y comenzamos a navegar por distintas Webs, sin detectar nada anómalo hasta que entramos en una web bancaria, donde el navegador se cierra y nos aparece la siguiente ventana de Windows, bastante elaborada a nivel gráfico para simular ser una pantalla auténtica del sistema de seguridad de los sistemas Windows:


Al pinchar sobre “Aceptar” se abre una pequeña aplicación con título “Microsoft Internet Security Banking Connections”, tal como vemos en la siguiente captura:


Si se pincha sobre el botón inferior derecho “Activar a mi equipo la conexión segura…” se abre una nueva ventana donde se invita a introducir los datos de nuestra tarjeta de crédito. En nuestro caso se introdujeron (obviamente) datos falsos:


Tras esto, capturamos el tráfico de red que se generaba al hacer click sobre “Activar”, obteniendo como resultado que el malware emplea el recurso “send.php” de la web “elequipodelbarrio.com.ar”, enviando los datos de la tarjeta de crédito en la variable “data” por POST mediante protocolo HTTP. Esto puede verse en la siguiente captura de pantalla:


 Al acceder a la web “elequipodelbarrio.com” vemos que aparenta ser una Web legítima con comentarios de varias personas incluidos, tal como se puede ver en la siguiente imagen:


Lo que hace pensar en dos posibles opciones: que la web haya sido comprometida o que se trate de una falsa web para no generar alarma. En mi caso procedí a enviar la alerta al CSIRT-Cv para que gestionasen la alerta temprana.

Para finalizar indicar que se trata de un troyano “típico” sudamericano, más enfocado a intentar engañar al usuario inexperto con pantallas y un entorno gráfico bastante conseguido, que a emplear métodos realmente complejos como es el caso habitual de los troyanos rusos. Lo que me ha llamado la atención es que al subir el binario a VirusTotal no me indicó que hubiera sido subido con anterioridad por otro usuario, y a su vez, que solo 6 de los 42 antivirus hayan reconocido al Troyano como tal:



Me gustaría poder realizar un reversing del troyano para saber si reconoce la URL bancarias por dirección de URL, por ciertos caracteres de texto que identifican un banco o por cualquier otro motivo, pero por desgracia mañana me llega un libro muy esperado “El temor de un hombre sabio” y es una tarea que va a tener que esperar unos días.

PD: entrada publicada también en securityartwork (blog de mi empresa).
PD2: me confirman desde el CSIRT-cv que han cerrado la Web, un micro punto para los "buenos" xD

Continuar...

lunes, 7 de noviembre de 2011

HoneyNet UML

A lo largo de estos últimos años hemos escrito sobre los honeypots y las ventajas que aportan a nuestra infraestructura. Por ello para intentar promulgar el uso de este tipo de herramientas he preparado un entorno virtual que emula varios honeypots de distintas clase formando una red completa: una honeynet.

Como tecnología de virtualización he escogido User Mode Linux por estar bastante familiarizado con ella, debido a que fue la opción escogida en mi proyecto de fin de carrera.

Pero no solo por esto, ya que UML tiene una ventaja muy importantes respecto al resto de tecnologías: la máquina virtual es ejecutada con permisos de usuario sin privilegios, y por tanto, en caso de que un atacante consiga saltar al entorno anfitrión, éste accedería como usuario no privilegiado, es decir, sin ser root. De ahí su nombre, User Mode, ya que se ejecuta en el espacio de usuario y no en el espacio del kernel.

Como se puede ver en el siguiente diagrama, la Honeynet UML ha sido dividida en tres subredes: red de baja interacción, red de alta interacción y red de monitorización.


  • La red de baja interacción está formada por dos máquinas virtuales y un Hub UML. La primera tiene configurado un HoneyD con 3 plantillas diferentes: Solaris, Windows Server 2003 y un Cisco ASA. A su vez se ha empleado el honeypot Kippo que simula una shell de SSH. La segunda máquina UML tiene instalado un Dionaea que recientemente analizó Nelo.
  • La red de alta interacción está compuesta por una máquina virtual y un Hub UML. En ésta se ha instalado un honeypot de alta interacción (HIHAT) sobre un PhpMyAdmin.
  • La red de monitorización está compuesta por una máquina virtual y un Hub UML. La máquina UML “Monit” tiene instalado una consola centralizada por Web con un OSSEC, Nagios3, Syslog centralizado , monitorización del tráfico de red, consola de gestión HIHAT y un Snort 2.9.1.2 con DAQ 0.62 que analiza el tráfico de las redes de baja y alta interacción.
Para poder ejecutar este entorno es necesario configurar primeramente el equipo anfitrión para crear las tres interfaces del entorno y habilitar el Masquerade de las máquinas virtuales:

# /honeynet/sbin/setup.sh on
 Making Taps...
  Tap0... [OK]
  Tap1... [OK]
  Tap2... [OK]
 Internet...  [OK]

Una vez configurado las tres interfaces ya podemos lanzar la red virtual como usuario no privilegiado mediante la siguiente orden:

$ /honeynet/bin/honeynet.sh on
 
Dando como resultado la siguiente imagen, donde podemos ver a la izquierda las cuatro consolas de las cuatro máquinas virtuales UML, en la parte central superior la ejecución del script de la honeynet, en la parte central inferior las 4 terminales de las máquinas virtuales y por último a la derecha la consola de alertas BASE (Snort) notificando de conexiones a la MySQL de HiHat:
 



Para facilitar la administración he creado una consola web centralizada en el entorno de monitorización que nos permite gestionar las alertas generadas:
  • Entorno BASE que nos permite gestionar las alertas de Snort.
  • Interfaz Web de OSSEC para gestionar las alertas del HIDS.
  • Gestión de las notificaciones del Syslog de todos los honeypots distribuidos mediante Rsyslog y LogAnalyzer.
  • Monitorización de la disponibilidad del entorno con Nagios3.
  • Status que muestra el estado de la red: que tipo de tráfico, paquetes perdidos, clasificación de IPs conectadas, etc.
Tal como se ilustra en la siguiente imagen (al pinchar sobre la imagen, ésta se abrirá en una nueva ventana en tamaño grande):


He intentado subir el entorno a algún servidor para que únicamente con descargarse el fichero, descomprimirlo y ejecutarlo, tal como hemos mostrado en las capturas de pantalla, tendríamos una Honeynet completa en nuestro entorno, pero el fichero completo ocupa 6 GB y me ha sido imposible alojarla en un servidor.

De forma temporal he subido los ficheros necesarios a Google Code, donde podéis descargar desde este enlace los scripts necesarios para la ejecución y configuración de la honeynet. Para crear las máquinas UML “solo” tenéis que seguir estos pasos, que todo sea dicho, es un poco quick and dirty con un inglés sin comentarios ya que la wiki fue creada en dos horas…

Si alguien tiene especial interés existe la posibilidad de subir el proyecto a Megaupload o algún otro servidor que permita alojar un fichero de 6 GB. No me gusta nada esta idea pero visto lo visto no veo otra opción.

Para finalizar indicar que el proyecto está abierto a cualquier persona que quiera colaborar, ya que —sea dicho de paso— toda la colaboración que la “comunidad” muestra en aspectos de hacking y reversing brilla por su ausencia en materia de defensa de entornos. Por alguna razón desconocida (corramos un tupido velo) en general son más populares las estrategias y técnicas de ataque que las de defensa. Vayan ustedes a saber porqué.

PD: entrada publicada para securityartwork.

Continuar...

domingo, 16 de octubre de 2011

Fijación de sesión: Cookie (III)


Cuando la sesión se encuentra alojada en la cookie resulta mucho más difícil de explotar, debido a que cualquier navegador Web básico emplea la protección de mismo origen o “origin server”, donde un recurso web solo puede modificar atributos si ese recurso pertenece a su mismo dominio, puerto y protocolo, es decir, el servidor atacante no puede modificar la cookie del servidor víctima, y por tanto, no podrá fijar su sesión a la víctima.

Aún así existen distintas formas de intentar explotar una fijación de sesión almacenada en una cookie:

1. Si el servidor también es vulnerable a un XSS… ¿para qué voy a explotar un XSS para fijar una sesión cuando con explotar únicamente el XSS puedo obtener directamente la sesión empleada por la víctima?

2. Si puedo modificar el tráfico de red entre la víctima y el servidor vulnerable. Sinceramente ocurre lo mismo que en el caso anterior.

3. Pasar por GET la sesión y esperar un tratamiento incorrecto de la variable, ya que algunas aplicaciones Web si leen la variable de sesión por GET modifican la variable en la cookie.
4. Inyectando código HTML y JS en la cabecera del navegador, en especial “meta http-equiv” para intentar modificar la cookie. Esto funcionaba hace unos años, ahora mismo a mí no me ha dado buen resultado.

5. Intentado poner la cookie en la URL de tal forma que entre el recurso vulnerable y la cookie haya un salto de línea añadido “\r\n”, de modo que parezca que el servidor interprete la URL como URL más cabecera cookie. Era una vulnerabilidad relativamente antigua, tampoco me ha funcionado.

Por tanto, y desde mi punto de vista, intentar explotar una fijación de sesión por cookie es bastante difícil ya que se depende muchas veces de que el servidor, el navegador web de la víctima o la aplicación Web tenga alguna vulnerabilidad adicional.

Y ahora la pregunta que se estarán realizando, ¿por qué este post de una vulnerabilidad conocida y tanto interés en explotar la vulnerabilidad si la sesión va en una cookie? La respuesta es sencilla, en un viaje de vuelta en el tren me puse a jugar con una aplicación Web que sirve para practicar auditoría Web: DVWA. Como todas estas aplicaciones tienen una página de acceso al reto protegida por usuario y contraseña, la cual en principio no es vulnerable.

En caso de la DVWA creo que es vulnerable a fijación de sesión, ya que ésta no cambia antes y después de autenticarnos. Por precaución busqué si alguien había comentado algo previamente pero no he encontrado ningún comentario al respecto. Lógicamente esta aplicación no está enfocada a estar jamas en producción y su único propósito es docente. Aunque no tiene ningún tipo de criticidad puede servir como ejemplo.

Veámoslo en las siguientes dos capturas. Primero accedemos al recurso “login.php” sin estar autenticados, en la variable PHPSESSID vemos el valor de la sesión:



Una vez autenticamos el valor de la sesión continua siendo la misma:




Por desgracia al ir en la cookie la sesión me fue imposible explotar la vulnerabilidad. Para finalizar agradecer la ayuda proporcionada por Raúl Siles para intentar fijar la sesión en la cookie y recomendar esta presentación sobre fijación de sesión (PDF), a cuya charla en Barcelona tuve el placer de asistir. Nos vemos en la próxima entrada.


PD: entrada publicada para securityartwork.


Continuar...

miércoles, 12 de octubre de 2011

Fijación de sesión: PoC (II)


Tal como se describió en la anterior entrada, la fijación de sesión es una vulnerabilidad debida a un tratamiento incorrecto en la gestión de sesiones por parte de una aplicación Web. En concreto la vulnerabilidad radica en que cuando se concede una sesión a un usuario no autenticado, y éste se autentica en dicha aplicación, el valor de la sesión no cambia. Por tanto, el vector de ataque para explotar la vulnerabilidad consiste en intentar que la víctima se autentique en la aplicación usando una sesión previamente conocida por el atacante, de tal forma que una vez el usuario se autentique, el atacante pueda acceder a la aplicación con el mismo rol que la víctima gracias a que conoce la sesión de ésta.

Para comprender de forma más sencilla el funcionamiento de la vulnerabilidad vamos a presentar una posible prueba de concepto, donde el recurso web “login.php” es vulnerable a la fijación de sesión en un escenario con un servidor web vulnerable, una víctima, un atacante y un servidor web del atacante. Veamos por paso el funcionamiento mediante un ejemplo:

1. El atacante envía un correo a la víctima haciéndose pasar por un compañero de trabajo y diciéndole que por favor consulte un dato de una aplicación, proporcionándole un enlace a la supuesta aplicación, cuando realmente el enlace apunta al servidor del atacante.
2. La víctima pincha en el enlace y por tanto accede al servidor del atacante.
3. El servidor del atacante al detectar la conexión de la víctima se conecta al servidor vulnerable para obtener una sesión valida.
4. Una vez obtenida la sesión, redirecciona a la víctima al servidor vulnerable pero forzándola a usar la sesión que el ha obtenido previamente.
5. La víctima se autentica en la aplicación vulnerable. Tened en cuenta que para la víctima el paso tres y cuatro son transparentes.
6. El atacante accede a la aplicación vulnerable con la sesión que le proporciono a la víctima.

Para ver la prueba de concepto de forma práctica he creado un pequeño script en python, llamado “sessionFixation.py” (), que hace las funciones del servidor atacante de forma automatizada. Éste acepta dos argumentos: el recurso vulnerable y el nombre de la variable de sesión. Al ejecutar el script se levanta en el puerto 80 un servidor web a la espera de recibir peticiones GET, y por tanto, a la espera de una víctima. Cuando la víctima se conecta al servidor, este de forma automática obtiene una sesión valida del servidor vulnerable y redirecciona a la víctima forzándole a usar por GET la sesión previamente fijada por el atacante.

En nuestro caso, el atacante será 192.168.67.130 y el servidor vulnerable 192.168.67.129. Situaremos un proxy Web delante del navegador de la víctima para ver los pasos que realiza su navegador y ejecutaremos elscript del servidor teniendo en cuenta que en nuestro caso el recurso vulnerable es “login.php” y la variable de sesión es “PHPSESSID”, por tanto para ejecutarlo se haría de la siguiente forma:

# ./sessionFixation.py “192.168.67.129/login.php” PHPSESSID

La víctima, cuya IP es 192.168.67.1, pinchará sobre el enlace que apunta al servidor del atacante (http://192.168.67.130) realizando una petición como la siguiente:

GET / HTTP/1.1
Host: 192.168.67.130 (servidor atacante)

Al conectarse la victima al servidor atacante, éste realiza una consulta al recurso vulnerable y obtiene de su respuesta una sesión válida. A continuación, mediante código JavaScript, redireccionará a la víctima al recurso “login.php” pero forzando a usar la sesión previamente obtenida:

GET /login.php?PHPSESSID=25imduuvgq8kbtrl1bl8mfg8b1 HTTP/1.1
Host: 192.168.67.129 (Servidor vulnerable)
Referer: http://192.168.67.130/ (redireccionados por servidor atacante)

Este proceso es totalmente invisible para el usuario víctima, puesto que lo único que vera es que el enlace le ha llevado al recurso legítimo de “login.php”, tal como se muestra en la siguiente captura de pantalla, donde podemos ver el log del servidor atacante en la parte superior y el navegador de la víctima en la parte inferior, después de explotar la vulnerabilidad:


Como vemos, el script fija la sesión por GET, pero en caso que fuera necesario fijarla por POST habría que cambiar un par de líneas para responder a la víctima con un formulario en vez de con un “document.location”. Es relativamente sencillo realizar esta operación.

¿Pero qué ocurre cuando la sesión se encuentra en la cookie? Lo veremos en la próxima entrada.

PD: entrada publicada para securityartwork

Continuar...

domingo, 9 de octubre de 2011

Fijación de sesión (I)


El protocolo HTTP fue diseñado para cumplir unos requisitos muy limitados, sin tener en cuenta muchas de las necesidades de la actualidad; por ejemplo, el protocolo no es capaz inicialmente de proporcionar o restringir visibilidad sobre sus recursos dependiendo de qué usuario los solicite.

Cuando un usuario accede a un servidor web éste realiza el saludo a tres bandas, solicita un recurso al servidor mediante HTTP, el servidor le devuelve el recurso solicitado y se cierra la conexión, por lo que si pinchamos en un enlace de la web, el servidor lo vería como una petición nueva y desconocerá que somos el mismo usuario. Por ello fue necesaria la creación del concepto de sesión, de tal forma que el servidor web sepa que somos el mismo usuario y en función de la sesión, proporcione visibilidad sobre unos recursos y con un determinado formato.

Esta sesión es un valor que debe ser aleatorio, difícil de adivinar y con una caducidad temporal. Normalmente la sesión irá como parámetro de la URL, en las cookies o en métodos POST; la sesión será generada y proporcionada por el servidor, mientras que el cliente web tendrá la obligación de añadir la sesión en sus peticiones para que el servidor tenga constancia de quién está realizando la solicitud. Por ejemplo, imagínese que usted se autentica en su webmail de tal forma que se le permita leer su correo electrónico y por algún tipo de vulnerabilidad, por ejemplo un XSS, alguien tiene acceso a su sesión. Este atacante podrá tener las mismas funcionalidades en el recurso web que tiene usted. Su usuario y contraseña solo le sirvieron para indicarle al servidor Web que conceda a su sesión visibilidad sobre su correo.

Por ello existen multitud de posibles vectores de ataque para que un potencial atacante intente obtener su sesión y, por tanto, sea capaz de suplantar a su usuario; principalmente estos se centran en los siguientes tres tipos:

1. Sesión predecible: Si la generación de la sesión no es aleatoria un atacante puede predecirla y suplantar al usuario.

2. Lectura de la sesión: La sesión puede ser accedida cuando hay alguna vulnerabilidad en la aplicación web, como un cross site scripting, o por emplear protocolos sin cifrado en entornos donde un potencial atacante podría leer el tráfico de red. Así se entiende el riesgo que supone acceder a un recurso web donde el proceso de autenticación es cifrado (HTTPS), pero una vez autenticados, navegamos sin cifrar (HTTP) y por tanto nuestra sesión también lo hace en texto en claro.

3. Fijación de sesión. Si una aplicación web, sin estar autenticados en el entorno, nos concede un identificador de sesión, y al autenticarnos el valor de esta sesión no cambia, estaremos ante una vulnerabilidad de fijación de sesión.

En la siguiente entrada documentaremos con mayor extensión cómo funciona la fijación de sesión, proporcionando una prueba de concepto real sobre esta vulnerabilidad.

PD1: entrada publicada también para securityartwork.es.
PD2: siento el vacio de Septiembre pero entre la mudanza y la preparación del GWAPT me ha sido imposible escribir nada. Por cierto, ¡ya soy GWAPT! :P


Continuar...

miércoles, 24 de agosto de 2011

Introducción a la creación de preprocesadores Snort (IV)

Por fin llega el último post de creación de preprocesadores dinámicos en Snort ¿y que mejor forma que coincidiendo con que se ha vuelto a editar el manual de Snort en HTML (http://manual.snort.org/)?

La entrada de hoy es posiblemente la más práctica pero también más largas de las anteriores, ya que crearemos un ejemplo sencillo de un preprocesador dinámico donde se indicará, tanto en la fase de preprocesador como en la de motor de reglas, que si detecta tráfico de una cierta IP que hemos indicado, tanto de origen como destino, se genere una alerta de Snort.

Evidentemente esto ya lo hace Snort con una regla tonta, pero es un ejemplo sencillo que explica bastante bien como funciona Snort, luego ya se puede desarrollar el preprocesador que necesitéis donde el entorno actual de Snort no os permita crearlo mediante reglas. Por ejemplo de esto y que se ha visto ya en el blog fue el preprocesador de geolocalización que permitía generar alertas dependiendo del origen y destino de la alerta.

Para este procesador usaremos el del ejemplo pero editando el fichero blog.c desde cero. Podeis usar el que tenemos y vereis que la estructura es muy parecida simplemente se han cambiado un par de cosas adaptandolo al ejemplo. Recordar el diagrama de la entrada anterior, es importantísimo para esta entrada.

Empecemos revisando el fichero sf_preproc_info.h, el cual tiene que tener una estructura como la siguiente:



#ifndef SF_BLOG_INFO_H_
#define SF_BLOG_INFO_H_

#define MAJOR_VERSION   1
#define MINOR_VERSION   0
#define BUILD_VERSION   1
#define PREPROC_NAME    "Blog"

#define DYNAMIC_PREPROC_SETUP   BlogSetup
extern void BlogSetup();

#endif /* SF_BLOG_INFO_H_ */

Como vemos lo más importante es que hemos declarado el preprocesador dinámico Blog, cuya versión es la 1.0 y hemos indicado que la función de arranque del preprocesador es BlogSetup el cual se encuentra definido en "blog.c".

A partir de ahora nos centraremos en la implementación del preprocesador Blog que se encuentra en el fichero "blog.c". El código está compuesto por cinco bloques virtuales:

1º) Librerías (si ya se, bibliotecas) y declaración del preprocesador.
2º) Configuración de arranque del preprocesador.
3º) Acciones a realizar en el módulo de preoprocesado.
4º) Acciones a realizar cuando es llamado en el motor de reglas.
5º) Acciones a realizar cuando es rearrancado.


Vamos a desarrollar poco a poco el preprocesador empezando por los 5 módulos documentados:

1º) Librerías y declaración del preprocesador:

Empecemos con la primera parte del preprocesador, las librerias:

//Estandar C
#include <stdlib.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>

//Preprocesadores Snort
//Configuración
#include "config.h"
//Para la devolución del valor al hacer match en una regla
#include "sf_snort_plugin_api.h"
#include "preprocids.h"
//Paquete de red que lee el Snort
#include "sf_snort_packet.h"
#include "sf_dynamic_preprocessor.h"
//Politica de los preprocesadores
#include "sfPolicy.h"
#include "sfPolicyUserData.h"

//Soporte inet_ntoa - ÑAPA / PARCHE GUARRO DE SNORT!!!
#undef inet_ntoa
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//Trabajar con sfip_t
#include "sf_ip.h"
#include "sf_ip.c"

A continuación declaramos el ID del preprocesador. Éste es el valor que se usa cuando queramos limitar o eliminar ciertas alertas del preprocesador empleando por ejemplo el tag "suppress". Cada procesador dinámico tiene un identificador único para él, incluido el propio motor de reglas cuyo id es el 1. Para nuestro ejemplo pondremos el número "300" (como la peli):

//GID del preprocesador
#define GENERATOR_ID 300

En el siguiente paso se declaran los ID y textos que se mostraran en caso de que se quiera generar una alerta en la fase de preprocesado. En nuestro caso crearemos la regla 1 del preprocesador 300 con el texto de alerta "He detectado tráfico de la IP indicada", tal como se muestra a continuación:

//Alertas en la fase de preprocesado
#define ALERT_BLOG 1
#define ALERT_BLOG_STR "He detectado tráfico TCP de la IP indicada"

En este punto también declararemos las estructuras que sean necesarias para el preprocesador. En nuestro caso será un puntero a char donde almacenaremos la IP leida en el fichero de configuración:
//Donde almacenaremos la IP indicada en la configuración del Snort:
char *IPPROC;

Para finalizar inicializaremos las variables de Snort necesarias como es el propio preprocesador, las políticas (usar las del ejemplo) y declararemos las funciones que vamos a usar en todo el preprocesador:

//Politicas
tSfPolicyUserContextId ex_config = NULL;

//Si no es un reinicio
#ifdef SNORT_RELOAD
    tSfPolicyUserContextId ex_swap_config = NULL;
#endif

//Estructura de nuestro preprocesador (sería el this en OO)
extern DynamicPreprocessorData _dpd;

//Funciones empleadas en el preprocesador
//Inicialización del preprocesador
static void BlogInit(char *);
//Acciones a realizar en la fase de preprocesado
static void BlogProcess(void *, void *);
//Acciones a realizar cuando cargamos el motor de reglas (lectura de reglas)
static int BlogRuleInit(char *, char *, void **);
//Acciones a realizar al tratar un paquete que hace match con una regla
static int BlogRuleEval(void *, const uint8_t **, void *);
//Esta parte es la que hemos dicho que no tocaríamos del reload, corresponde al quinto punto
#ifdef SNORT_RELOAD
static void BlogReload(char *);
static int BlogReloadSwapPolicyFree(tSfPolicyUserContextId, tSfPolicyId, void *);
static void * BlogReloadSwap(void);
static void BlogReloadSwapFree(void *);
#endif


2º) Configuración de arranque del preprocesador:

Si nos fijamos pusimos una función en el fichero "sf_preproc_info" de nombre "BlogSetup". Esa función es la primera función que llama Snort cuando arranca, éste dependiendo de si es un inicio o un recarga de Snort registra el preprocesador con "registerPreroc".

Para registrar un preprocesador necesitamos principalmente dos argumentos:
  • El primero corresponde con el nombre declarado en la variable "PREPROC_NAME" del fichero "sf_preproc_info" que es el empleado en el fichero de configuración de Snort. En nuestro ejemplo "Blog".
  • El segundo argumento indica el nombre de la función de inicio principal del preprocesador encargada de registrar todas las acciones que ejecutará éste. En nuestro ejemplo "BlogInit".
Veamos la implementación a continuación:

//Registro del preprocesador y inicialización de este
void BlogSetup(void)
{
    #ifndef SNORT_RELOAD
    //Registramos el preprocesador Blog y llamamos a la funcion BlogInit
    _dpd.registerPreproc("Blog", BlogInit);
    #else
    _dpd.registerPreproc("Blog", BlogInit, BlogReload,
        BlogReloadSwap, BlogReloadSwapFree);
    #endif
}

La siguiente función, BlogInit es la encargada real de inicializar el preprocesador, donde se registrará las funciones a usar en la fase de preprocesado mediante "addPreproc" y en la fase del motor de reglas mediante "preprocOptRegister". Para el ejemplo solo usaremos una de cada pero se pueden emplear más de una.

La función BlogInit soporta un único argumento que es un puntero a char, el cual contiene un puntero al texto existente en el fichero de configuración de Snort, y por tanto, contendrá las variables de configuración de nuestro preprocesador. En nuestro ejemplo será donde tendremos IP que queremos que genere alerta en caso de detectarse tráfico hacia o desde esa IP:

/* Inicialización del preprocesador */
static void BlogInit(char *args)
{
    //Variable para argumentos
    char *arg;
    char *config;

    //Politicas de uso
    tSfPolicyId policy_id = _dpd.getParserPolicy();
    if (ex_config == NULL)
    {
        ex_config = sfPolicyConfigCreate();
        if (ex_config == NULL)
            _dpd.fatalMsg("\tFallo en la inicialización del preprocesador, no puede reservar la estructura.\n");
    }

    //Obtenemos las opciones del preprocesador indicadas en el fichero snort.conf
    arg = strtok(args, " \t\n\r,");
    if(arg && !strcasecmp("miip", arg))
    {
      arg = strtok(NULL, " \t\n\r,");
      //Copiamos la IP que queremos que genere alerta
      IPPROC = (char *)calloc(1, sizeof(arg));
      strncpy(IPPROC, arg, strlen(arg));
      _dpd.logMsg("    La IP que generará alerta es %s\n",IPPROC);
    }
    else
    {
      _dpd.fatalMsg("    Blog: Opcion invalida %s\n");
    }
   
    //Seteamos políticas, a muerto un gatito
    config = arg;
    sfPolicyUserPolicySet(ex_config, policy_id);
    sfPolicyUserDataSetCurrent(ex_config, config);


    //Mensaje del preprocesador de salida indicando que vamos a registrar ya las acciones
    _dpd.logMsg("Blog config:\n");
    _dpd.logMsg("    Joaquin Moreno, el Moxi Sexy - 2011\n");

    //Declaración de funciones a procesar en la fase de preprocesador
    //Funcion, Prioridad, ID de la función - es única entre todos los preprocesadores, protocolos tratados
    _dpd.addPreproc(BlogProcess, PRIORITY_TRANSPORT, 10000, PROTO_BIT__TCP | PROTO_BIT__UDP | PROTO_BIT__ICMP);

    //Declaracion de reglas y evaluación de las mismas en el motor de reglas
    //Nombretag, Función de inicialización, Función de Evaluación de la regla, NULL, NULL, ...
    _dpd.preprocOptRegister("miip", BlogRuleInit, BlogRuleEval, NULL, NULL, NULL, NULL, NULL);

    //Mensaje que se mostrará cuando cargue el preprocesador
    DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "Preprocessor: Blog OK. Hola mama salgo en Interné :P\n"););
}

Como vemos, para registrar la función que se ejecutara en la fase de preprocesador usamos la función "addPreproc" cuyas opciones son las siguientes:
  • BlogProcess: nombre de la función que se ejecutara en la fase de preprocesador por cada paquete de red leido.
  • PRIORITY_TRANSPORT: prioridad del preprocesador.
  • 10000: corresponde con el identificador único de registro de la función. Mucho cuidado porque es GLOBAL para todo Snort... es decir, si tengo un preprocesador 300 donde registro la función número 1000 y creo un preprocesador 301 donde registro la función número 1000 ¡fallará!.
  • PROTO_BIT__TCP | PROTO_BIT__UDP | PROTO_BIT__ICMP: para que protocolos debe saltar esta función, en nuestro caso para TCP o UDP o ICMP.
A su vez para registrar la función que se ejecutará en la fase del motor de reglas usamos "preprocOptRegister" cuyas opciones son las siguientes:
  • "miip": es el tag identificativo de la regla. Un tag se construye en la regla como "tag:valor;", por ejemplo en content:"Hola"; el tag es "content". Por tanto si queremos usar nuestro preprocesador en el motor de reglas añadiríamos a la regla: miip:"127.0.0.1"; (siendo 127.0.0.1 una ip de ejemplo).
  • BlogRuleInit: es la función que se ejecuta por cada regla que lee Snort durante su arranque que contiene el tag indicado en la primera opción. Es decir, cuando arrancamos Snort leemos los ficheros de reglas, pues por cada regla que contenga el tag "miip" se ejecutará esta función. El objetivo de ésta es leer los parametros que hay indicados en la regla y configurarlos para ser procesados posteriormente por cada paquete que llegue al motor de reglas mediante la función "BlogRuleEval". 
  • BlogRuleEval: es la función que se ejecutara por cada paquete de red que llega al motor de reglas y ha cumplido la parte de la izquierda de la regla (esta parte es donde se indica el protocolo, puertos y ips destino / origen). Es la encargada de indicar si el tag se cumple o no. Una regla se activa si todos los tags son ciertos. 
  • "El resto": las restantes 5 opciones ponerlas a Null, no son requeridas para un ejemplo básico.

3º) Acciones a realizar en el módulo de preoprocesado:

Tal como se indico en el punto 2, la función BlogProcess que ha sido registrada mediante "addPreproc" será la que se ejecutara por cada paquete de red leido en la fase de preprocesado. Está función soporta dos opciones aunque únicamente nos interesa la primera. "void *pkt". la cual es un puntero a una estructura SFSnortPacket donde se encuentra normalizado el paquete leido:

void BlogProcess(void *pkt, void *context)
{
    //Paquete leído
    SFSnortPacket *p = (SFSnortPacket *)pkt;

    //Política de prioridades
    sfPolicyUserPolicySet(ex_config, _dpd.getRuntimePolicy());

    //Se debe comprobar que realmente el paquete es un paquete IP
    if(p->ip4h == NULL)
    {
        //No es un paquete IP
        if(p->ip4_header == NULL)
        {
                return;
        }
        else
        {
             //Arpa never die!, estructura in_addr
           //Si la IP origen o destino es la indicada en el fichero de configuración de snort generamos un alerta
  if((strcmp(IPPROC, inet_ntoa((p->ip4_header->destination))) == 0) || (strcmp(IPPROC, inet_ntoa((p->ip4_header->source))) == 0) )
           {
               _dpd.logMsg("Acertado: %s\n", ALERT_BLOG_STR);
               _dpd.alertAdd(GENERATOR_ID, ALERT_BLOG, 1, 0, 1, ALERT_BLOG_STR, 0);
               return;
            }
        } //fin Else de la condición (
p->ip4_header == NULL)
    } //Incio Else de la condición p->ip4h == NULL
    else
    {
        //Snort Never Die!, estructura snort
        //Si la IP origen o destino es la indicada en el fichero de configuración de snort generamos un alerta
  if((strcmp(IPPROC, sfip_to_str(&((p->ip4h->ip_dst)))) == 0) || (strcmp(IPPROC, sfip_to_str(&((p->ip4h->ip_src)))) == 0) )
        {
            //Generamos alerta de preprocesador (ID_GEN, ID_RULE, VER_RULE, REV_RULE, Priority, MSG, X)
            _dpd.alertAdd(GENERATOR_ID, ALERT_BLOG, 1, 0, 1, ALERT_BLOG_STR, 0);
         }
      return;
    }
}

Lo que hace la función anterior es buscar si la IP origen o destino del paquete es la misma que hemos indicado en el fichero snort.conf, entonces ejecutamos la función "alertAdd" encargada de generar un alerta a nivel de preprocesador. La explicación de los argumentos de la función son los siguientes:
  • GENERATOR_ID: es la variable que contiene el ID del preprocesador (300).
  • ALERT_BLOG: id de la regla en preprocesado (1).  
  • 1: versión de la alerta.
  • 0: revisión de la alerta.
  • 1: prioridad, dejar tal cual.
  • ALERT_BLOG_STR: cadena de texto que mostrará Snort como alerta. 
  • 0: nada, no nos interesa.
4º) Acciones a realizar cuando es llamado en el motor de reglas:

Primero se trabaja con la función de inicialización de las reglas de nuestro preprocesador, la cual se ejecuta cuando las reglas son leidas durante el arranque de Snort. El nombre de esta función es BlogRuleInit, la cual fue registrada mediante "preprocOptRegister". BlogRuleInit soporta los siguientes argumentos:
  • name: nombre de la regla. No se suele usar.
  • params: argumento de la opcion de la regla, lo que ha escrito a la derecha del tag en la regla, es decir si hemos puesto "miip:127.0.0.1;" entonces args contendría "127.0.0.1".
  • data: variable donde guardamos los datos que nos interesa de la regla, para pasarselo posteriormente a la función que se ejecutará en el motor de reglas por cada paquete leido.
La implementación se mostrará a continuación:

static int BlogRuleInit(char *name, char *args, void **data)
{
    //Guardaremos la IP como un simple char, aunque normalmente se usan estructuras complejas
    char *miip = NULL;

    if(args == NULL)
    {
      //Mensaje de fallo
      _dpd.fatalMsg("Ande vas pallo malvado!!!\n");
    }
    else
    {
      miip = (char *)calloc(1, sizeof(args));
      strncpy(miip, args, strlen(args));
      _dpd.logMsg("La IP leida es %s\n", miip);
    }

    //Almaceno la IP indicada en la regla
    *data = (void *)miip;

  return 1;
}

El código de la función anterior es sencillo, leemos el String "args" que contiene la configuración del preprocesador, por ejemplo: "miip 127.0.0.1" y es almacenada en el doble puntero data.

El siguiente función será llamada por el motor de reglas si por cada paquete que llegue a éste la parte de la izquierda de una regla se cumpla y en la parte de la derecha éste el tag que hemos usado para registrar la regla. En este caso el tag es "miip". Esta función la hemos llamado "BlogRuleEval" la cual fue registrada empleando "preprocOptRegister". Esta función soporta los siguientes argumentos:
  • pkt: contiene el paquete que estamos analizando en la regla.
  • cursor: ni caso.
  • data: contiene los valores que guardamos en el tercer argumento de la función "BlogRuleInit", es decir, los valores de la regla.
Veamos la implementación:

static int BlogRuleEval(void *pkt, const uint8_t **cursor, void *data)
{
    SFSnortPacket *p = (SFSnortPacket *)pkt;
    char * miip = (char *) data;
    if(p->ip4h == NULL)
    {
        if(p->ip4_header == NULL)
        {
                return RULE_NOMATCH;
        }
        else
        {
                //Soporte Arpa
  if((strcmp(miip, inet_ntoa((p->ip4_header->destination))) == 0) || (strcmp(miip, inet_ntoa((p->ip4_header->source))) == 0) )
                {
                      _dpd.logMsg("Acertado!");
                     return RULE_MATCH;
                 }
        }
    }
    else
    {
           //Soporte Snort
  if((strcmp(miip, sfip_to_str(&((p->ip4h->ip_dst)))) == 0))) || (strcmp(miip, sfip_to_str(&((p->ip4h->ip_src)))) == 0) )
           {
                _dpd.logMsg("Acertado!");
                 return RULE_MATCH;
            }
    }
    //Salimos si no cumple
    return RULE_NOMATCH; 
}

Como vemos es muy parecida a la regla de evaluación en la fase de preprocesado, pero en este caso la configuración está guardada en la variable "data". Así mismo para indicar que este tag se cumple se devuelve la constante "RULE_MATCH" o en caso contrario se devuelve "RULE_NOMATCH". Para que una regla se cumpla todos los tags que está tenga declarado debe cumplirse.


5º) Acciones a realizar cuando es rearrancado:

En ésta parte dejaremos tal cual está la del ejemplo puesto que su explicación requeriría más de una entrada solo para este punto. Digamos que nuestro preprocesador no funcionará si hacemos un reload, por tanto será necesario parar y rearrancar Snort. Si vemos ejemplos veremos que es prácticamente implementar lo mismo que hemos visto pero teniendo en cuenta un par de cosas. Veamos la implementación:

#ifdef SNORT_RELOAD
static void BlogReload(char *args)
{
    char *config = NULL;
    tSfPolicyId policy_id = _dpd.getParserPolicy();

    _dpd.logMsg("blog dynamic preprocessor configuration\n");

    if (ex_swap_config == NULL)
    {
        ex_swap_config = sfPolicyConfigCreate();
        if (ex_swap_config == NULL)
            _dpd.fatalMsg("Could not allocate configuration struct.\n");
    }

    sfPolicyUserPolicySet(ex_swap_config, policy_id);
    sfPolicyUserDataSetCurrent(ex_swap_config, config);

    DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, "Preprocessor: blog is initialized\n"););
}

static int BlogReloadSwapPolicyFree(tSfPolicyUserContextId config, tSfPolicyId policyId, void *data)
{
    char *policy_config = (char *)data;

    sfPolicyUserDataClear(config, policyId);
    free(policy_config);
    return 0;
}

static void * BlogReloadSwap(void)
{
    tSfPolicyUserContextId old_config = ex_config;

    if (ex_swap_config == NULL)
        return NULL;

    ex_config = ex_swap_config;
    ex_swap_config = NULL;

    return (void *)old_config;
}

static void BlogReloadSwapFree(void *data)
{
    tSfPolicyUserContextId config = (tSfPolicyUserContextId)data;

    if (data == NULL)
        return;

    sfPolicyUserDataIterate(config, BlogReloadSwapPolicyFree);
    sfPolicyConfigDelete(config);
}
#endif

Una vez escrito nuestro preprocesador lo compilamos y lo instalamos:

# make
# make install

Vamos a declarar el preprocesador indicando que notifique en la fase de preprocesado si detecta tráfico de la IP 8.8.8.8. Para ello editamos el fichero de configuración de Snort (/etc/snort/snort.conf) y añadimos como último preprocesador (el orden de posicionamiento del preprocesador SI que importa) la siguiente línea:

preprocessor Blog: miip 8.8.8.8

Para crear una regla de ejemplo que generará una alerta si el paquete es TCP, el puerto es 80 y la ip de origen o destino es la 8.8.8.8:
alert tcp any any -> any 80 (msg:"Hola gente!"; miip:8.8.8.8; sid: 1; rev: 1;)

Una vez configurado inicializaremos Snort:

# /usr/local/bin/snort -u snort -g snort -c /etc/snort/snort.conf -i eth0 -l /var/log/snort

Running in IDS mode

        --== Initializing Snort ==--
...
  Loading dynamic preprocessor library /usr/local/lib/snort_dynamicpreprocessor/lib_sfdynamic_preprocessor_blog.so... done
...
Blog config:
    Joaquin Moreno, el Moxi Sexy - 2011
La IP que generará alerta es 8.8.8.8
...
      --== Initialization Complete ==--
...
Preprocessor Object: Blog  Version 1.0  <Build 1>
...
(Snort se queda a la escucha)

Posteriormente para probar el funcionamiento del preprocesador en la fase de preprocesado se he realizado un simple ping donde se muestra dos alertas a nivel de preprocesado, por un lado la ida y por otro la vuelta correspondiente al tráfico ICMP 8 y 0 del ping:

# ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_req=1 ttl=54 time=39.0 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 39.075/39.075/39.075/0.000 ms

Mostrando por la consola de Snort las siguientes dos notificaciones:


Acertado: He detectado tráfico TCP al puerto indicado
Acertado: He detectado tráfico TCP al puerto indicado

Y si accedemos al log de Snort veremos que se han generado dos alertas a nivel de preprocesador, donde se nos indica que ha sido el GID 300, regla 1, revision 1 ([300:1:1]):

[**] [300:1:1] He detectado tráfico TCP al puerto indicado [**]
[Priority: 1]
08/23-21:56:02.628184 10.0.0.1 -> 8.8.8.8
ICMP TTL:64 TOS:0x0 ID:0 IpLen:20 DgmLen:84 DF
Type:8  Code:0  ID:29225   Seq:1  ECHO

[**] [300:1:1] He detectado tráfico TCP al puerto indicado [**]
[Priority: 1]
08/23-21:56:02.668564 8.8.8.8 -> 10.0.0.1
ICMP TTL:54 TOS:0x0 ID:9947 IpLen:20 DgmLen:84
Type:0  Code:0  ID:29225  Seq:1  ECHO REPLY

Para probar la regla he realizado lo siguiente:
# printf "GET / HTTP/1.0\n\n" | nc 8.8.8.8 80 > /dev/null
(PD: Tened en cuenta que con esta acción también se cumple los requisitos del preprocesador, para no liar solo se muestra las alertas desde el motor de reglas)

Al no recibir respuesta desde el puerto 80 solo se detecta el paquete de la ída, teniendo como resultado en la pantalla de Snort la siguientes entrada:

Acertado!

Y como alerta de Snort:

[**] [1:1:1] Hola gente! [**]
[Priority: 0]
08/23-22:01:43.613062 10.0.0.1:49149 -> 8.8.8.8:80
TCP TTL:64 TOS:0x0 ID:23450 IpLen:20 DgmLen:60 DF
******S* Seq: 0x155736A2  Ack: 0x0  Win: 0x16D0  TcpLen: 40
TCP Options (5) => MSS: 1460 SackOK TS: 2701226025 0 NOP WS: 6

Bueno ahora ya sí, con esto hemos termino la introducción a la implementación de preprocesadores dinámicos. Realmene lo importante ahora es tener buenas ideas y cumplir requisitos que son necesarios en nuestra infraestructura, pero que Snort por defecto no nos lo ofrece. En mi caso, y gracias a tener a unos cracks en el curro, tenemos uns preprocesadores que son la bomba, parece que no, pero estos entornos permiten hacer muchísimas cosas y detectar cosas que por defecto un Snort no puede.

Hasta la proxima, que espero no sea dentro de mucho aunque teniendo en cuenta que empiezo el Inglés y que tengo el examen  del 542 de SANS me parece a mí que hasta finales de Septiembre la cosa estará difícil ;)



Continuar...

sábado, 20 de agosto de 2011

Introducción a la creación de preprocesadores Snort (III)

Hola de nuevo, esta es la tercera entrada de introducción a la implementación de un preprocesador dinámico, donde veremos las funciones principales de un preprocesador dinámico, su función y el orden de llamadas de éstas.

Como hemos visto a lo largo de las entradas pasadas éste tiene como mínimo dos ficheros:

  • sf_preproc_info.h: este nombre no se debe cambiar. Es donde se declara el preprocesador, su nombre, su número de registro y donde indica la función de inicialización para empezar la carga del preprocesador. Esta función se encuentra implementada en el fichero "nombrePreprocesador.c".
  • nombrePreprocesador.c: es el fichero donde se encuentra la implementación del preprocesador, en nuestro caso se llama "blog.c".

El funcionamiento básico de ambos ficheros se explica en el siguiente diagrama resumido:

Fichero sf_preproc_info.h:
  ...
  extern void BlogSetup();
  ...

Fichero blog.c:
  -> extern DynamicPreprocessorData _dpd;

  -> void BlogSetup(){ ...  _dpd.registerPreproc("Blog", BlogInit); ...}

      -> BlogInit(char * args)
      { ...
      "DeclaracionEstructuas" ,
      "Declaracion de politicas" , 
      "Lectura del parametro 'args' que contiene la configuración de snort.conf",
      "Declaramos la funcion que se ejecuta para cada paquete en el lado del preprocesado:
          _dpd.addPreproc(hacerEnPreproc,opc2,...);",
      "Para cada paquete leido que hacemos en el motor de reglas
          _dpd.preprocOptRegister("tag regla", leerReglaFichero,hacerEnRegla,...);"
       
        ... }

             -> void hacerEnPreproc(el_paquete, ...) { ... acciones ... }

             -> static int leerReglaFichero(nombreRegla,valoresRegla, GuardamosDatos)           
            -> static int hacerEnRegla(paquete, ..., LoGuardado) { ... acciones ... }

Veamos paso a paso que es exactamente lo que está ocurriendo:


1º) En el fichero "sf_preproc_info.h" se indica la función del fichero "nombrePreprocesador.c" que inicializa el preprocesador. En nuestro caso la función es "BlogSetup" la cual fue declarado en el fichero "sf_preproc_info.h" como "extern void BlogSetup();".

Por tanto una vez se carga la función "BlogSetup" todo el resto de la implementación que veremos a continuación estará en el fichero "nombrePreprocesador.c", es decir "blog.c".

2º) La función "BlogSetup()" es la encargada de registrar el preprocesador mediante "registerPreproc" de la estructura "DynamicPreprocessorData",  indicando como segundo argumento el nombre de la función que se encargara de empezar la carga del preprocesador; a esta función la llamaremos "BlogInit" y se declararía de la siguiente forma: "_dpd.registerPreproc("Blog", BlogInit)".

Dentro de esta función, dependiendo de si se hace un "Start" o un "Reload" del servicio Snort se cargara una u otra función de inicialización. Para que se entiende forma sencilla, si yo ejecuto Snort en modo start se carga la función "_dpd.registerPreproc("Blog", BlogInit);", si en cambio es un reload de un Snort ya en ejecución haremos  un "_dpd.registerPreproc("Blog", BlogInit, BlogReload, BlogReloadSwap, BlogReloadSwapFree);", tal como se muestra a continuación:

void BlogSetup(void)
{
    #ifndef SNORT_RELOAD
        _dpd.registerPreproc("Blog", BlogInit);
    #else
        _dpd.registerPreproc("Blog", BlogInit, BlogReload, BlogReloadSwap, BlogReloadSwapFree);
    #endif
}

Para estas entradas solo trataremos el caso de inicialización pero es importante entender que las funciones reload son obligatorias para el arranque del Snort por tanto se dejaran tal cual están las del ejemplo que proporciona Snort.

3º) La función "BlogInit" carga los datos necesarios para el preprocesador durante el arranque del Snort, por ejemplo, la creación de estructuras necesarias, lectura de ficheros, lectura de los parametros de configuración del preprocesador indicados en el fichero de configuración de Snort (/etc/snort/snort.conf) y declarar las políticas del preprocesador. También se registran las funciones que se van a ejecutaran en el módulo del preprocesado y en módulo del motor de reglas al leer un paquete de red. El primero se crea mediante la función "addPreproc" y el segundo mediante la función "preprocOptRegister", ambas pertenecientes a la estructura "DynamicPreprocessorData".

Para asegurar que se ha entendido el funcionamiento de cada una de las funciones vamos a repasarlo mediante un pequeño diagrama hecho con DIA (siendo esta forma más visual), ya que, entender el funcionamiento del core de un preprocesador dinámico es la base de la implementación de éstos:


     
Con esto ya disponemos de las nociones básicas del funcionamiento de un preprocesador.

Para finalizar vamos a mostrar dos pequeñas funciones de la estructura "DynamicPreprocessorData" (entre muchas comillas por que ya os habréis dado cuenta que no es realmente una estructura) que permite mostrar texto en la implementación de forma que podamos seguir la traza del preprocesador. Éstas son "logMsg" y "fatalMsg". Ambas muestran por pantalla texto y el valor de las variables, pero "fatalMsg" adicionalmente finaliza la ejecución de Snort, por ejemplo, al detectar un fallo:

Si no hay error:
    _dpd.logMsg("La IP mostrada es %s\n",IPPROC);
sino
      _dpd.fatalMsg("Fallo en la aplicación, no puedo continuar\n");

Estas funciones son importantes porque las usaremos mucho durante la implementación de Snort.

Con esto ya hemos explicado las funciones principales de un preprocesador y como son llamadas unas a otras para realizar una cierta tarea. En la siguiente entrada, y última, se expondrá el código de un procesador "chorra" que sirva de ejemplo de como se implementa. Nos leemos.



Continuar...

jueves, 18 de agosto de 2011

Introducción a la creación de preprocesadores Snort (II)

Para esta segunda entrado vamos a familiarizarnos con el funcionamiento de las dos principales estructuras de Snort requeridas durante la programación de un preprocesador dinámico:

DynamicPreprocessorData

Es el propio preprocesador dinámico, lo que sería "this" en un lenguaje OO. Lo usaremos para añadir las acciones que se van a realizar durante la fase de lectura o inicialización de la configuración del preprocesador, lectura de los ficheros de reglas, procesado de cada paquete durante la fase del módulo de preprocesado y en el módulo del motor de reglas.

Ésta estructura es declarada en el fichero principal del preprocesador (blog.c) después de los include y de forma global mediante la siguiente sentencia: "extern DynamicPreprocessorData _dpd;". Por tanto, a lo largo de la implementación del preprocesador trataremos con la variable "_dpd".

SFSnortPacket: 

Es la estructura del paquete de red que ha sido leído por el sniffer y desglosado por los normalizadores en un formato de estructura C. Ésta se encuentra definida en el fichero "/usr/src/snort-version/src/dynamic-preprocessors/include/sf_snort_packet.h".

Cuando trabajemos con SFSnortPacket es muy importante tener en cuenta los siguientes tres puntos:



1ª) No todos los campos de la estructura existen, solo se rellenan aquellos campos que han sido requeridos por el paquete leído. Si hemos leído un paquete UDP los campos del paquete TCP y ICMP serán nulos (NULL) y por tanto no se podrá acceder a ellos.

Si por ejemplo no tenemos en cuenta que Snort podría leer un paquete ARP e intentamos leer la IP destino de un paquete, el cual se encuentra en: "SFSnortPacket -> ip4_header -> destination", el preprocesador fallara al intentar acceder a un dato no inicializado puesto que ip4_header valdrá "null" dando lugar a un "segmentation fault" y la caída del proceso Snort. Por tanto siempre tenemos que comprobar que la estructura está inicializada, en este caso que "ip4_header != NULL" antes de intentar acceder al campo "destination" de la estructura "ip4_header".

2ª) Dependiendo de como hayamos compilado Snort y de la versión de este (2.8 o 2.9) tenemos que tener especial cuidado con que estructura usamos, ya que dependiendo de éste la información del paquete se encontrará en distintas estructuras dentro de la macro estructura SFSnortPacket.

Por ejemplo en unos entornos la información de la cabecera IP se encontrará en "SFSnortPacket -> ip4h" en otros estará en "SFSnortPacket -> ip4_header". Por lo que si queremos tener compatibilidad entre versiones y compilaciones deberemos usar una comprobación como la siguiente (hay mas cosas a tener en cuenta, pero esta es la principal):


    if(p->ip4h == NULL)
    {
        if(p->ip4_header == NULL)
        {
        //No es un paquete IP
                return;
        }
        else
        {
        //El paquete IP está en p->ip4_header
        Código
        }
    }
    else
    {
      //El paquete IP está en p->ip4h
      Código
    }


3º) Otro punto importante relacionado con SFSnortPacket, y en mi opinión, me parece una autentica barbaridad está relacionado justamente con esa diferencia entre la estructura "ipv4" y "ip4_header" puesto que el primero usa el formato propio de snort "sfip_t" y el segundo usa el formato arpa "in_addr":


typedef struct _IPV4Header
{
    u_int8_t version_headerlength;
    u_int8_t type_service;
    u_int16_t data_length;
    u_int16_t identifier;
    u_int16_t offset;
    u_int8_t time_to_live;
    u_int8_t proto;
    u_int16_t checksum;
    struct in_addr source;
    struct in_addr destination;
} IPV4Header;


typedef struct _IPv4Hdr
{
    u_int8_t ip_verhl;      /* version & header length */
    u_int8_t ip_tos;        /* type of service */
    u_int16_t ip_len;       /* datagram length */
    u_int16_t ip_id;        /* identification  */
    u_int16_t ip_off;       /* fragment offset */
    u_int8_t ip_ttl;        /* time to live field */
    u_int8_t ip_proto;      /* datagram protocol */
    u_int16_t ip_csum;      /* checksum */
    sfip_t ip_src;          /* source IP */
    sfip_t ip_dst;          /* dest IP */
} IP4Hdr;

Y diréis, ¿qué problema hay con esto? pues que la gente de Snort ha renombrado las funciones de Arpa sustituyéndolas por las del sfip_t. Es decir, imaginaros que queremos pasar una IP a Ascii y estamos usando la estructura "IPV4Header" pues si intentamos usar la función "inet_ntoa" (network to Ascii) para obtener la IP en formato texto, esta fallará porque la función "inet_ntoa" ha sido renombrada por "sfip_ntoa", y lógicamente ésta espera una estructura sfip_t y no una estructura in_addr puesto que estas no son compatibles entre si:


# grep inet_ntoa /usr/src/snort-version/src/dynamic-preprocessors/include/ipv6_port.h
#ifdef inet_ntoa
#undef inet_ntoa
#define inet_ntoa sfip_ntoa

Para poder saltarse esta restricción, debemos añadir como últimos include del fichero de implementación del preprocesador, en nuestro caso "blog.c", las siguientes líneas de código:

//Soporte inet_ntoa!!!
#undef inet_ntoa
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

Desconozco si hay una forma menos guarra de hacerlo pero viendo el soporte y la ayuda de los foros y listas de Snort es lo mejor que he podido hacer. Si tenéis alguna otra forma más elegante soy todo oídos (no vale declarar una función copia de "inet_ntoa" con otro nombre :P).

Una vez explicada estas dos estructuras, realizaremos un último cambio para personalizar más todavía nuestro ejemplo. Para ello editaremos el fichero "sf_preproc_info.h":


Modificamos esto:
...
define DYNAMIC_PREPROC_SETUP   ExampleSetup
extern void ExampleSetup();
...


Por esto:
...
define DYNAMIC_PREPROC_SETUP BlogSetup
extern void BlogSetup();
...

A continuación se deberá modificar el fichero "blog.c":


# sed -e "s/example/blog/g" blog.c >> blog2.c
# mv blog2.c blog.c
# sed -e "s/Example/Blog/g" blog.c >> blog2.c
# mv blog2.c blog.c

Para finalizar limpiaremos la última compilación mediante un "make clean" y comprobaremos que el cambio fue realizado con éxito:

# make clean
# make

Bueno con esto ya tenemos un poco las nociones básicas de las dos estructuras principales que usaremos en Snort, por lo que tal como he comentado en la proxima entrada ya empezaremos a meternos ya con las funciones de Snort. Espero que os esten gustando estas entradas, slds ;)



Continuar...