scieee AI-readable full text Open interactive document viewer

Implementación y medida del rendimiento de un firewall para aplicaciones web (WAF) en un balanceador de carga

Cano Blanco, Álvaro

Abstract

Debido la masificación de clientes en Internet, y a los inevitables cortes que un servicio puede sufrir por su propia naturaleza, la tecnología está cambiando de paradigma. Los nuevos conceptos tecnológicos como son Big data, machine learning o data science, se basan en aplicaciones distribuidas con las que paliar las saturación de usuarios y dotar a la aplicación de alta disponibilidad, con la que se garantiza el acceso al servicio y de calidad. Este paradigma ha sido muy bien adoptado por las empresas, que ven en él una forma de ganarse la confianza de clientes, ya que éstos perciben un servicio de calidad, la empresa dispone de alta disponibilidad en aplicaciones críticas y se abaratan costes, debido a que la infraestructura escala fácilmente en función a la demanda de clientes. El rol que tiene el balanceador de carga como elemento centralizador del tráfico que circula hacia las aplicaciones distribuidas, lo hace ideal como punto en el cual aplicarles las políticas de seguridad. Esto presenta varias mejoras en el mantenimiento y rendimiento de la aplicación, como son: que la gestión de la seguridad se hace en un único nodo; se valida la información que reciben las aplicaciones, haciendo posible que la comunicación con ésta pueda viajar sin cifrar; y se aprovechan algunas de las operaciones del proxy Web que son compartidas con el WAF (Web Application Firewall) con lo que se elimina latencia. Con este proyecto se ha dotado a un balanceador de carga de capacidades WAF (Web Application Firewall), utilizando una librería de inspección de contenido que permite configurar reglas con las que proteger los servidores frente a amenazas y vulnerabilidades. El desarrollo se ha llevado a cabo teniendo en cuenta que el rendimiento fuese lo más óptimo posible y que el servicio de balanceo no sufra cortes durante la configuración de las políticas de seguridad, dado su criticidad. Para abstraerse de la librería y darle al operador de seguridad una herramienta fácil e intuitiva, se ha creado una API HTTP con la que pueda aplicar configuraciones sin tener que conocer la semántica de reglas usada por la librería elegida. Esta API puede ser utilizada para diversas tareas, como integrarse en otros sistemas, por ejemplo, un panel de control; o automatizar tareas a través de scripting.

Full text

I Equation Chapter 1 Section 1 Proyecto Fin de Grado Ingeniería de Telecomunicaciones Implementación y medida del rendimiento de un Firewall para aplicaciones Web (WAF) en un balanceador de carga Autor: Álvaro Cano Blanco Tutor: Antonio Jesús Sierra Collado Dpto. Ingeniería Telemática Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2013 II III Proyecto Fin de Grado Ingeniería de Telecomunicación Implementación y medida del rendimiento de un Firewall para aplicaciones Web (WAF) en un balanceador de carga Autor: Álvaro Cano Blanco Tutor: Antonio Jesús Sierra Collado Profesor Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2018 IV V Proyecto Fin de Carrera: Implementación y medida del rendimiento de un Firewall para aplicaciones Web (WAF) en un balanceador de carga Autor: Álvaro Cano Blanco Tutor: Antonio Jesús Sierra Collado El tribunal nombrado para juzgar el Proyecto arriba indicado, compuesto por los siguientes miembros: Presidente: Vocales: Secretario: Acuerdan otorgarle la calificación de: Sevilla, 2018 VI El Secretario del Tribunal VII Agradecimientos A mis amigos, a mi familia, a mis compañeros y a mis profesores. En definitiva a todos aquellos de los que alguna vez aprendí algo. Álvaro Cano Blanco Sevilla, 2018 VIII IX Resumen Debido la masificación de clientes en Internet, y a los inevitables cortes que un servicio puede sufrir por su propia naturaleza, la tecnología está cambiando de paradigma. Los nuevos conceptos tecnológicos como son Big data, machine learning o data science, se basan en aplicaciones distribuidas con las que paliar las saturación de usuarios y dotar a la aplicación de alta disponibilidad, con la que se garantiza el acceso al servicio y de calidad. Este paradigma ha sido muy bien adoptado por las empresas, que ven en él una forma de ganarse la confianza de clientes, ya que éstos perciben un servicio de calidad, la empresa dispone de alta disponibilidad en aplicaciones críticas y se abaratan costes, debido a que la infraestructura escala fácilmente en función a la demanda de clientes. El rol que tiene el balanceador de carga como elemento centralizador del tráfico que circula hacia las aplicaciones distribuidas, lo hace ideal como punto en el cual aplicarles las políticas de seguridad. Esto presenta varias mejoras en el mantenimiento y rendimiento de la aplicación, como son: que la gestión de la seguridad se hace en un único nodo; se valida la información que reciben las aplicaciones, haciendo posible que la comunicación con ésta pueda viajar sin cifrar; y se aprovechan algunas de las operaciones del proxy Web que son compartidas con el WAF (Web Application Firewall) con lo que se elimina latencia. Con este proyecto se ha dotado a un balanceador de carga de capacidades WAF (Web Application Firewall), utilizando una librería de inspección de contenido que permite configurar reglas con las que proteger los servidores frente a amenazas y vulnerabilidades. El desarrollo se ha llevado a cabo teniendo en cuenta que el rendimiento fuese lo más óptimo posible y que el servicio de balanceo no sufra cortes durante la configuración de las políticas de seguridad, dado su criticidad. Para abstraerse de la librería y darle al operador de seguridad una herramienta fácil e intuitiva, se ha creado una API HTTP con la que pueda aplicar configuraciones sin tener que conocer la semántica de reglas usada por la librería elegida. Esta API puede ser utilizada para diversas tareas, como integrarse en otros sistemas, por ejemplo, un panel de control; o automatizar tareas a través de scripting. XVI ÍNDICE DE TABLAS Tabla 1. Campos comunes para todas las reglas 40 Tabla 2. Campos específicos para reglas 'mark' 40 Tabla 3. Campos para reglas 'action' y „match-action' 41 Tabla 4. Campos específicos de reglas 'match-action' 41 Tabla 5. Campos de configuración de un set. 43 Tabla 6. Campos de configuración de un set API 52 Tabla 7. Tabla de parámetros para cada tipo de regla 53 Tabla 8. Tabla de peticiones de la API 54 Tabla 9. Tabla de códigos HTTP para la API 54 Tabla 10. Posibles valores de un match. 55 Tabla 11. Características de la máquina anfitriona de las pruebas de rendimiento 59 Tabla 12. Resultados de pruebas de rendimiento 60 Tabla 13. Hosts del escenario “usos prácticos” 68 XVII XVIII ÍNDICE DE FIGURAS Figura 1. Balanceador de carga bloqueando petición HTTP. 2 Figura 2. Balanceador repartiendo tráfico entre un pool de backends. 4 Figura 3. Balanceador redirigiendo tráfico tras fallo en un backend. 4 Figura 4. Modelo de capas de un balanceador de carga 5 Figura 5. Lógica del balanceador y el WAF 11 Figura 6. Interacción entre la API y el WAF 12 Figura 8. Diagrama del main de Pound 15 Figura 9. Secuencia de acciones de do_http 16 Figura 10. Insertar lógica de WAF en proceso Pound 18 Figura 11. Comunicación entre Pound y Pounctl 20 Figura 12. Esquema de una petición HTTP 26 Figura 13. Funcionamiento de la función "do_http" tras el desarrollo WAF. 29 Figura 14. Arquitectura API 32 Figura 15. Esquema para obtener un objeto set de reglas 44 Figura 16. Esquema de la función "parse_rule" 45 Figura 17. Esquema de la función "build_set" 48 Figura 18. Escenario de pruebas de rendimiento 58 Figura 19 . Gráfico de peticiones respecto al número de directivas analizadas 60 Figura 20. Resultado del test de configuración 67 Figura 21. Resultado del test funcional de sistema 68 Figura 22. Escenario para usos prácticos 69 Figura 23. Esquema SQL injection 75 Figura 7 . Diagrama de Gannt 84 XIX 1 1 INTRODUCCIÓN Nos ha tocado vivir un mundo basado en la tecnología, y, ¿qué mejor forma de entenderlo, que a través de sus comunicaciones? 1.1 Presentación El crecimiento de los usuarios y dispositivos en Internet, está degenerando en problemas de acceso a los servicios que ésta alberga. Constantemente nos encontramos con servicios de baja calidad, debido a la saturación de clientes o incluso cortes de servicios, que pueden ir desde minutos hasta semanas. Esta degeneración de los servicios puede ser debido a varios factores como:  Límite de recursos en la aplicación.  Problemas en la infraestructura.  Operaciones de mantenimiento.  Explotación de vulnerabilidades.  Ataques de denegación de servicio, DoS. Ante esto, las empresas empiezan a demandar soluciones tecnológicas con aplicaciones distribuidas que permitan un rápido escalado de la infraestructura, a la vez, que puedan dar un servicio más robusto que no sea amenazado con cortes de servicio o que disminuya su calidad. En la implementación de estas tecnologías empiezan a aparecer nombres como Big Data, High Availability, o Data Science, en los que el balanceador de carga es el elemento que se encarga de centralizar el tráfico y orquestarlo de forma óptima entre los distintos nodos de procesamiento de datos. De cara a la seguridad, actualmente, existe una demanda continua de sistemas que ofrezcan propiedades capaces de detectar y prevenir los distintos tipos de ataques. Entre los ataques más comunes y eficaces, se encuentran los que van dirigidos a las aplicaciones Web (SQL Injection, DoS, CSS, fuerza bruta…) y ante los cuales, el WAF, es la herramienta usada como elemento de protección. El WAF es un elemento flexible, que puede configurarse en función de la aplicación que esté protegiendo. Es un sistema que aplica una política (bloquear o continuar) a una transacción HTTP cuando se cumple un determinado patrón. La asociación de estos patrones y políticas se hace mediantes reglas de configuración que serán comprobadas en cada trama HTTP que se dirija o salga de la aplicación. Debido a que el WAF necesita desempaquetar las tramas HTTP para buscar patrones, se presenta una oportunidad de mejora del sistema al aprovechar que el LB (Load Balancer) también necesita realizar esta tarea. 1.2 Motivación Debido a la complejidad que añade la seguridad a las infraestructuras y los sistemas, el interés de este proyecto radica en dar a los operadores de seguridad una herramienta útil e intuitiva que permita aplicar configuraciones 2 de seguridad en el balanceador de carga. Sin tener que mantener reglas y directivas de seguridad en cada uno de los nodos de procesameinto. En este proyecto se dará un primer alcance para esta herramienta. Éste es que puedan ser examinados los campos HTTP más frecuentes y se les puedan aplicar condiciones que repercutan en aceptación o denegación de la petición entrante a la aplicación Web. 1.3 Objetivos El objetivo de este proyecto es dar capacidades de WAF (Web Application Firewall) a un balanceador de carga, con el sentido de que el balanceador pueda aceptar o denegar peticiones de clientes si detecta alguna amenaza. Para ello el desarrollo se apoyará en software libre, que consistirá en un software de balanceo llamado Pound, con licencia GPL (General Public License), y una librería de análisis de tramas HTTP llamada Libmodsecurity, con Licencia Apache. Ya que la librería necesita obtener los datos HTTP de forma ordenada, se aprovechará el parseo HTTP que hace Pound durante el procesamiento de las peticiones. Como herramienta para la configuración de esta solución, se creará una API HTTP en Perl, que permita de una format intuitiva gestionar las reglas WAF. Esta API podría ser consumida posteriormente por un recurso Web, que dotará al sistema de una interfaz gráfica de gestión. Por último se validará el desarrollo mediante:  Una herramienta de tests funcionales para la API.  Casos prácticos de detección y prevención de algunos ataques Web.  Tests de rendimiento de la integración. WAF LB Figura 1. Balanceador de carga bloqueando petición HTTP. 3 4 2 ESTADO DEL ARTE El proyecto gira entorno a dos ejes principales, los balanceadores de carga y los sistemas WAF (Web Application Firewall), por ello en este punto se detalla en qué consiste cada una de estas soluciones y se indagará en algunas de las implementaciones que ahora mismo hay disponibles en el mercado. 2.1 Balanceador de carga Un balanceador de carga es un dispositivo de red. Su función principal es la de hacer de elemento virtualizador del servicio, una interfaz a la que se conectan los usuarios, para posteriormente ser repartidos entre distintas unidades reales del servicio. Otras de las funciones importantes del balanceador, es la de comprobar el estado de todos los recursos que tiene disponible, así si alguno de ellos estuviese saturado, o fuera de servicio, el balanceador no reenviaría más peticiones a este recurso. LB Figura 2. Balanceador repartiendo tráfico entre un pool de backends. LB Figura 3. Balanceador redirigiendo tráfico tras fallo en un backend. 5 La última de las propiedades que hacen al balanceador tan popular, es la rápida escalabilidad. La topología puede cambiar, deducirse o aumentar, en cuanto a procesamiento, con un bajo coste operativo y manteniendo el servicio sin cortes. 2.1.1 Balanceadores L4 y L7 A la hora de hablar sobre balanceadores, encontramos que existen dos tipos: L4 (Layer 4) y L7 (Layer 7). Con la palabra Layer nos hace referencia a la capa del modelo OSI en el que trabajan. Así el balanceador L4 trabaja en la capa de transporte, éstos actúan como un NAT, transformando la IP y puerto virtual del balanceador, en la IP y puerto real del recurso al que será redirigido. Al trabajar en una capa inferior a los L7, la complejidad del balanceo será menor, ya que aún no hay acceso a datos de nivel de aplicación. El balanceador L7, por el contrario, desempaqueta los datos de aplicación, por lo que tiene acceso a las cabeceras HTTP. Al haberlas desempaquetado, puede modificarlas, o incluso añadir o quitar. Su uso más común es como proxy HTTP pudiendo multiplexar varias aplicaciones en un solo servicio y redirigir la petición por su URI. A su vez balancean entre una lista de servidores que pueden alojar la petición. 2.1.2 Comparativa de soluciones para balanceo de carga Existen diferentes implementaciones para balanceo de carga dependiendo de las necesidades de cada usuario. Algunas de ellas están enfocadas en mover paquetes basándose en los datos obtenidos de las conexiones. Éstas corresponderían a balanceadores de carga en capa 4, dan soporte a múltiples propiedades pero sin poder profundizar en soluciones complejas. Por ejemplo es posible establecer persistencia por IP pero no por cookie o sesión. LVS (Linux Virtual Server) es un proyecto que está integrado en el kernel de Linux. Es capaz de repartir conexiones desde el mismo kernel, sin tener que usar datos de espacio de usuario que consumen mayor cantidad de recursos. Es un proyecto maduro que lleva desarrollándose desde 1998. Nftlb (Netfilter load balancer) es un proyecto basado en la nueva generación de firewalls de Linux, NFtables. CL BK LB IP TCP HTTP L4 L7 IP TCP HTTP IP TCP HTTP Figura 4. Modelo de capas de un balanceador de carga 12 Admin WAF rules CL BK LB WAF lib API Figura 6. Interacción entre la API y el WAF 13 14 4 INTEGRACIÓN ENTRE BALANCEADOR Y WAF Para realizar la integración, el primer paso será conocer el comportamiento de libmodsecurity y adaptarlo a la arquitectura de Pound. Libmodsecurity está formado por cuatro objetos principales: modsec, rules, transaction e interruption.  Modsec, es el elemento que crea e inicializa la estructura con la que comunicarse con la API de Libmodsecurity.  Rules, este objeto es el encargado de almacenar todos los sets de reglas que han sido cargados.  Transaction, es quién gestiona el paso de datos HTTP en una petición-respuesta. Las transacciones guardan las variables usadas por las reglas, las crean y les asignan valores en función de los paquetes HTTP recibidos. Cuando una petición-respuesta entre el cliente y el LB (Load Balancer) acabe, la transacción debe ser eliminada para que se liberen todos los recursos asociados a la petición y la respuesta. Por cada una de las fases de una transacción es necsario aplicar los siguientes pasos: o Indicar a la librería dónde se encuentra la información que ésta necesita, referenciándole cada uno de los datos HTTP recibidios, a través de funciones como: msc_add_n_request_header, waf_add_http_info o msc_append_request_body. o Dar la orden de procesar a Libmodsecurity, una vez que conoce los datos que debe analizar. Se utilizan las funciones: msc_process_uri, msc_process_request_body, msc_process_request_headers...  Interruption, es el objeto donde se guarda la resolución de una transacción y la acción que debería ser aplicada. Mediante la función msc_intervention es posible conocer el estado actual de una transacción. En el esquema de Pound se distinguen dos etapas de procesamiento. La primera es la inicialización de los módulos de Pound, en función de la configuración con la que se arrancó el binario. Una vez todo está configurado el proceso entra en un bucle el cual comprueba si ha llegado alguna petición nueva. En caso de que así sea, la petición es asignada a un hilo de procesamiento y encolado hasta que pueda ser gestionado. La segunda etapa es el la gestión del hilo que maneja el paso de mensajes entre el cliente y el servidor, añadiendo la lógica de balanceo y donde se añadirán comprobaciones del WAF (Web Application Firewall). Para aplicar acciones a un proceso de Pound que se encuentre corriendo, Pound cuenta con una herramienta llamada Poundctll encargada de comunicarse mediante un socket de control y enviarle comandos. Algunas de las acciones que Poundctl permite son: obtener estadísticas de conexiones, añadir backends o poner backends en mantenimiento. El esquema correspondiente a la primera etapa de procesamiento de Pound puede verse en la siguiente figura: 15 Las conexiones de Pound son gestionadas por un esquema de “una conexión, un hilo”. Este esquema reserva en la memoria del programa un espacio en el que se añadirán todos los datos correspondientes a la conexión. El hilo dependiendo del tipo de petición que haya llegado del cliente podrá tener un tiempo de vida mayor o menor. Por ejemplo, en el caso de HTTP/1.0 el hilo muere una vez el balanceador a reenviado la respuesta del servidor al cliente, mientras que en el caso de una petición corriente de HTTP/1.1 el canal podrá quedarse abierto hasta que o bien lo cierre el cliente o el backend. Esta lógica de “una conexión, un hilo” presenta un problema ya conocido, y es la penalización por interbloqueos, ya que cuando el programa cambia de contexto, de un hilo a otro, se producen bloqueos de acceso a la memoria. Cómo ventaja, presenta un esquema más sencillo que otros, cómo podría ser la programación por eventos. A continuación puede verse el diagrama de flujo simplificado que sigue Pound en la gestión de una conexión HTTP. En esta memoria no se tratará la lógica de balanceo o de comprobación del estado de los backends, ya que es transparente para la inspección de tráfico. Lee configuración Crea socket de control Inicializa los listeners Demoniza el proceso Acepta la conexión Crea un nuevo hilo para gestionar la conexión Añade el hilo al pool de conexiones Main de Pound Esperando una nueva conexión Figura 7. Diagrama del main de Pound 16 El código de Pound se encuentra repartido en los siguientes ficheros:  config.c, son las funciones que configuran Pound, trata tanto los argumentos de entrada, como el fichero de configuración.  http.c, tiene la lógica para la gestión de una la petición y respuesta entre un cliente y un backend.  svc.c y svc.h, son funciones donde se implementa la lógica de balanceo de carga y de elección del backend que servirá la petición del cliente. Implementa algunas características como: el control de sesiones, manipulación de cookies o funciones usadas en el proceso de Poundctl.  pound.h, son las definiciones de las estructuras de datos.  pound.c, main del proceso Pound.  poundctl.c, main del Proceso Poundctl.  waf.c, son las nuevas funciones implementadas para las características de WAF.  pound.8 y poundctl.8, manuales de los binarios Pound y Poundctl respectivamente.  config.h.in, configure.in, Makefile.in, ficheros de configuración de la herramienta autotools.  dh512.h, dh1024.h, definiciones para crear certificados de 512 y 1024 bytes respectivamente. En cuanto a la nomenclatura utilizada para el desarrollo de las nuevas funcionalidades de Pound, se ha seguido la siguiente estandarización.  Las nuevas funciones y variables comienzan por la etiqueta “waf_”. Estas funciones suelen apoyarse en funciones de Libmodsecurity por lo general. Durante el desarrollo del punto 2 de la memoria, se irán dando puntualizaciones sobre cómo y dónde son usadas. Para una explicación más detallada, de cómo actúa cada una de estas funciones, ver el Anexo 1.  Las funciones de Libmodsecurity tienen su propio nombrado, usándose la etiqueta “msc_”. Las funciones que han sido usadas de Libmodsecurity se explican en el Anexo 1. (CL <- LB) Abre el socket SSL con el cliente. (CL -> LB) Obtiene las cabeceras del cliente. Calcula servicio y backend al que debe dirigirse la petición. (LB -> BK) Conecta con el backend elegido. (LB -> BK) Envía las cabeceras al backend. (LB -> BK) Añade cabeceras de proxy. (CL -> LB -> BK) Reenvía el body de cliente a backend. (LB <- BK) Obtiene las cabeceras del backend. (CL <- LB) Envia las cabeceras al cliente. (CL <- LB) Añade cabeceras proxy al cliente. (CL <- LB <- BK) Reenvía el body de backend a cliente. Log del resultado de la comunicación. Si el protocolo es HTTP/1.1, se queda eperando una nueva petición. Figura 8. Secuencia de acciones de do_http 17 4.1 Makefile y compilación condicional A continuaciónn se adecuará el código de Pound para que pueda enlazar con la nueva librería, Libmodsecurity. La flag –I indica al compilador dónde buscar los ficheros de cabeceras y la flag –L dónde se encuentran los ficheros compilados de la librería. Enlace de las librerías Fichero: Makefile.in INCLUDES=-I/usr/local/modsecurity/include/ \ -L/usr/local/modsecurity/lib -Wl,-rpath=/usr/local/modsecurity/lib CC=@PTHREAD_CC@ -g -rdynamic $(INCLUDES) LIBS=@LIBS@ @PTHREAD_LIBS@ -lmodsecurity El nuevo desarrollo será condicional, para ello se utilizará una variable de compilación, que habilitará el código de WAF en caso de que la variable tenga un valor positivo durante el proceso de compilación del programa. Así, todos los bloques de código presentados en este punto 2 van englobados dentro de una sentencia IFDEF. Por limpieza de la memoria y legibilidad del código, estos flags no aparecerán en los bloques de código. Flag de compilación Fichero: Makefile.in C_WAF=-DWAF=1 CFLAGS=$(CFLAGS) ${C_WAF} OBJS=pound.o http.o config.o svc.o pound_sync.o waf.o 4.2 Inicialización y configuración Pound funciona cargando parte de su configuración en variables que son globales para todo el programa. Son estructuras a las que pueden acceder otros hilos de ejecución para comprobar la configuración del sistema y así gestionar las conexiones en consecuencia. Por tanto se crearán nuevas variables con la que gestionar la configuración del WAF. Estas variables son:  waf_rules, es una estructura cargada en memoria con los conjuntos de reglas configurados y parseados.  waf_api, es el objeto sobre el que se crean transacciones y que gestiona la lógica de Libmodsecurity.  waf_rules_file, es una lista enlazada con los path a los ficheros de reglas que han sido leídos del fichero de configuración de Pound. Variables globales 18 Fichero: Pound.c Rules *waf_rules; ModSecurity *waf_api; FILE_LIST *waf_rules_file; Siguiendo el diagrama de flujo de Pound en el proceso de arranque, se inicializaran los nuevos objetos durante el proceso de configuración de Pound. Se obligará a que los ficheros de reglas sean correctos, si ésto no se cumpliese, el programa no continuará con el arranque. Inicialización del módulo de WAF Fichero: pound.c, función: main() // WAF initializate waf API waf_api = msc_init(); // load rules if (waf_reload_rules()) exit(1); Para indicar a Pound la ruta a los ficheros de reglas de WAF, se ha añadido una nueva directiva en el fichero de configuración de Pound llamada WafRules. Se permite añadir tantas directivas WafRules como se necesiten, respetándose durante la ejecución el orden en que aparecen en el fichero de configuración. Esta directiva es parseada y guardada en la estructura de datos definida con anterioridad, llamada waf_rules_files. Lee configuración de LB Main de Pound Crea el socket de control Inicializa Libmodsecurity (waf_api) Carga reglas WAF (waf_rules) Figura 9. Insertar lógica de WAF en proceso Pound 19 Para parsear las directivas WafRules se utilizará la función config_parse_waf. La lógica para obtener este parámetro no se ha integrado en la función general de Pound encargada de parsear todo el fichero de configuración. Con esto se pretende poder recargar la configurción de WAF sin afectar al proceso de Pound. Ya que Pound no tiene funcionalidad de reload, se ha creado una exclusiva para recargar las directivas WafRules. Leer la configuración del WAF del fichero de configuración Fichero: config.c, función: config_parse_waf() void config_parse_waf() { char lin[MAXBUF]; FILE_LIST *file = NULL; FILE_LIST *bef_file = NULL; FILE_LIST *it = waf_rules_file; if( waf_rules_file != NULL ) { // Free all files struct while ( it ) { it = it->next; free ( waf_rules_file ); waf_rules_file = it; } waf_rules_file = NULL; } bef_file = NULL; regcomp(&WafRules, "^[ \t]*WafRules[ \t]+\"(.+)\"[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED); conf_init(conf_name); while(conf_fgets(lin, MAXBUF)) { if(!regexec(&WafRules, lin, 4, matches, 0)) { if ((file = (FILE_LIST *)malloc(sizeof (FILE_LIST)) ) == NULL ) conf_err("WAF config: out of memory - aborted"); else { lin[matches[1].rm_eo] = '\0'; file->file = strdup(lin + matches[1].rm_so); file->next = NULL; if(waf_rules_file != NULL) { bef_file = file; waf_rules_file = file; } else bef_file->next = file; } } } regfree(&WafRules); } 20 La opción de recargar las reglas WAF se hace mediante el binario Poundctl, usando la opción –R y el path al socket de control. Este path se define en la directiva Control del fichero de configuración de Pound. Un ejemplo de la ejecución podría ser: “poundctl –R –c /tmp/ctl_socket”. Ejecutar un reload de WAF no afectará a las conexiones que se encuentren establecidas, ya que la transacción encargada del análisis HTTP mantiene una copia del conjunto de reglas que existía en el momento que se creó. Acciones admitidas a través del controlador Fichero: Pound.h typedef enum { CTRL_LST, CTRL_EN_LSTN, CTRL_DE_LSTN, CTRL_EN_SVC, CTRL_DE_SVC, CTRL_EN_BE, CTRL_DE_BE, CTRL_ADD_SESS, CTRL_DEL_SESS, CTRL_FLUSH_SESS, REL_WAF } CTRL_CODE; Al igual que muchos otros programas Linux, Pound también utiliza la función getopt para leer los argumentos Pound Config_parse_waf () Waf_reload_rules () Socket control Conexiones abiertas Esperando nuevas conexiones Parsea argumentos entrada Poundctl Retrun error Aplica la acción Figura 10. Comunicación entre Pound y Pounctl 21 de entrada. La notación esperada por esta función consiste en un string en el que se encuentran todos los posibles valores de entrada. Si algún parámetro va seguido del carácter “:” quiere decir que ese parámetro requiere un valor, el cual irá a continuación. Leyendo los parámetros de entrada Fichero: Poundctl.c, función: main() while(!i && (c_opt = getopt(argc, argv, "c:LlSsBbNnXHR")) > 0) switch(c_opt) { case 'c': sock_name = optarg; break; case 'X': xml_out = 1; break; #if WAF case 'R': reload_waf = 1; break; #endif case 'L': if(is_set) usage(arg0); en_lst = is_set = 1; break; Al recibir el comando en Pound Fichero: srv.c, función: thr_control() #if WAF case REL_WAF: config_parse_waf(); waf_reload_rules(); break; #endif La función waf_reload_rules es la encargada de recargar los conjuntos de reglas usados por el proceso de Pound. waf_reload_rules carga los nuevos conjuntos de forma segura, en el caso de que se produjese algún error, ya sea por falta de espacio o por que las reglas estuviesen mal construidas, los conjuntos volverían al valor anterior. Debido a la complejidad de la sintaxis de SecLang, se ha desarrollado una opción para Pound, con la que arranque sólo para comprobar si las reglas o conjuntos están correctamente construidos. Para ello, Pound arrancará e intentará cargar la regla que se le manda como argumento con la opción –w, o el path al conjunto de reglas con la opción –W. En el caso de que consiga cargar la configuración de forma correcta, el programa saldrá con un 0. Por el contrario si detecta algún fallo, saldrá con un 1 mostrando un mensaje acorde al error. En ninguno de los casos el proxy llega a arrancar, sólo ejecuta la fase de configuración. Al igual que en el caso de Poundctl y su opción de reload, Pound también usa la función getopt para obtener los argumentos de entrada. 28 free(body_buff); body_buff=NULL; waf_body=0; /* had Transfer-encoding: chunked so read/write all the chunks (HTTP/1.1 only) */ } else if(cl_11 && chunked) { 4.3.5 Procesamiento de una respuesta HTTP El procesamiento de una respuesta HTTP es igual al de la petición. Las únicas características que cambian son:  No hay que crear una transacción, se usa la misma que se creó cuando se analizó la petición.  Tras analizar el cuerpo respuesta es obligatorio borrar la transacción, ya que si llegase una nueva petición debería crearse una nueva transacción.  En la respuesta no se procesa el verbo, URI o versión, en cambio se procesa el código HTTP. 4.4 Resumen Como conclusión, al finalizar la implementación detallada a lo largo de este punto, la interfaz de Pound presenta los siguientes cambios: Pound acepta dos nuevas directivas de configuración en sus ficheros de configuración:  WafRules, es la ruta a un fichero con reglas WAF. Puede haber tantas directivas de este tipo como se necesite añadir, éstas serán ejecutadas en el orden que aparecen en el fichero de pound.  WafBodySize, es el tamaño máximo que se comprobará para un body HTTP. Si el body excede este tamaño, se omitirá su comprobación. El binario Pound cuenta con dos nuevos argumentos de entrada para su ejecución:  W, para comprobar si un fichero de reglas es correcto y cumple los estándares de libmodsecurity.  w, pound recibe como argumento una regla SecLang y comprueba que su sintaxis sea correcta. El binario Poundctl, usado para interaccionar con un proceso ya corriendo de Pound cuenta con una nueva opción de ejecución:  R, esta opción hace que Pound recargue las directivas WafRules de su fichero de configuración. Las nuevas funciones, resultantes del desarrollo son las siguientes: Definición de funciones Fichero: pound.h extern void config_parse_waf(void); void waf_del_transaction(Transaction **transac); 29 void waf_create_transaction(Transaction **t, ModSecurity *ms, Rules *rules); void waf_reload_rules(void); int waf_body_enabled(int bodybuf, const char *logtag, int body_size, int chunked, int rpc); int waf_add_http_info(Transaction *t, const char *header); int waf_add_req_head(Transaction *t, char const **headers, int num_headers); int read_body(BIO *sock, char **buff, int size); int waf_resolution(Transaction *t,int *int_code, char **url); Por último, el esquema modificado con los cambios añadido de la función do_http, encargada de gestionar la conexión de un cliente: (CL <- LB) Abre el socket SSL con el cliente. (CL -> LB) Obtiene las cabeceras del cliente. (CL -> LB) Obtiene el body del cliente y lo almacena en un buffer. Calcula servicio y backend al que debe dirigirse la petición. (LB -> BK) Conecta con el backend elegido. Crear una transacción WAF. Inspecciona la petición del cliente. (LB -> BK) Envía las cabeceras al backend. (LB -> BK) Añade cabeceras de proxy. (CL -> LB -> BK) Reenvía el body de cliente a backend. (LB -> BK) Envía el body, almacenado en el buffer, al backend (LB <- BK) Obtiene las cabeceras de la respuesta del backend. (LB <- BK) Obtiene el body de la respuesta del backend. Inspecciona la respuesta del backend. (CL <- LB) Envia las cabeceras al cliente. (CL <- LB) Añade cabeceras proxy al cliente. (CL <- LB <- BK) Reenvía el body de backend a cliente. (CL <- LB) Envía el body de respuesta al cliente, que estaba almacenado en el buffer. Elimina la transacción WAF. Log del resultado de la comunicación. Si el protocolo es HTTP/1.1, se queda eperando una nueva petición. Figura 12. Funcionamiento de la función "do_http" tras el desarrollo WAF. 30 31 5 API DE CONFIGURACIÓN La finalidad de desarrollar una API, Application Programming Interface, es hacer el uso del WAF más intuitivo para aquellos usuarios que no estén familiarizados con la sintaxis SecLang. Uno de los requisitos de esta API es preservar la total compatibilidad con las sentencias SecLang, habiendo siempre la posibilidad de administrar las reglas WAF mediante la API o mediante sentencias SecLang propias de Libmodsecurity. También se permitirá importar ficheros de configuración de proveedores privados sin perder ninguna de sus características. En todo momento esta API será un apoyo, pudiéndose configurar las propiedades más comunes de Libmodsecurity, aunque no se tendrá el uso total de SecLang. Si el administrador necesitase ir más allá de lo que la interfaz permite, deberá utilizar directamente directivas SecLang con las que detallar comportamientos más específicos. 5.1 Diseño de la API El desarrollo de la API se ha hecho en lenguaje Perl. Perl es un lenguaje de scripting, de más alto nivel que otros lenguajes compilados como podría ser C. Perl está optimizado para la gestión de ficheros y la aplicación de expresiones regulares, por lo que lo hace ideal en la manipulación de archivos de configuración. Perl también destaca por la facilidad que permite a la hora de tratar datos, ya sea mediante hashes utilizados en el desarrollo para crear estructuras complejas, o slices, utilizados para gestionar listas. Por otra parte, el modelo de programación usado es una arquitectura modelo-controlador. Esta arquitectura separa el código en dos partes:  El modelo, es el encargado de la validación de los parámetros de entrada y la estandarización de los objetos de salida. El modelo no contiene ninguna lógica de aplicación.  El controlador, realiza toda la lógica de la aplicación. En este caso es una librería consumida por el modelo y que trabaja sobre los ficheros de configuración del WAF. La API cumple una estandarización RESTful pudiendo funcionar como un módulo de una aplicación mayor. Los verbos utilizados son:  GET, para listar objetos o mostrar su contenido.  POST, para crear objetos o aplicar acciones.  PUT, para modificar objetos.  DELETE, para eliminar objetos. El ciclo que seguiría una petición a la API se detallará en este punto y cumple el siguiente esquema general: 32 5.1.1 Servidor Web La API estará servida por un servidor Web que se comunicará con ella por medio de CGI. También se requiere que el servidor pueda soportar todos los verbos HTTP usados por la API (GET, POST, PUT y DELETE). Se ha elegido Apache2 por ser un servidor conocido y versátil, que se encuentra en los repositorios de Debian y su instalación es inmediata. $ sudo apt-get install apache2 Apache necesita activar la configuración CGI para poder servir aplicaciones basadas en esta tecnología. Ésto se consigue añadiendo un enlace simbólico desde las configuraciones disponibles a las configuraciones activas. $ sudo ln –s /etc/apache2/mods-available/cgi.load \ /etc/apache2/mods-enabled/cgi.load A continuación se reescribirá la ruta desde la se cargan los CGI a la que se encuentra el script de la API. Configuración de Apache Fichero: serve-cgi-bin.conf <IfModule mod_alias.c> <IfModule mod_cgi.c> Define ENABLE_USR_LIB_CGI_BIN </IfModule> <IfModule mod_cgid.c> Servidor Api.cgi Routes Modelo Librería HTTP Req Admin Figura 13. Arquitectura API 33 Define ENABLE_USR_LIB_CGI_BIN </IfModule> <IfDefine ENABLE_USR_LIB_CGI_BIN> ScriptAlias / /opt/waf/api/ <Directory "/opt/waf/api"> AllowOverride None Options +ExecCGI –MultiViews +SymLinksIfOwnerMatch Require all granted </Directory> </IfDefine> </IfModule> Por último es necesario reiniciar el servidor para cargar la nueva configuración. $ sudo /etc/init.d/apache2 restart 5.1.2 Script de ejecución La comunicación entre el servidor y la aplicación se hará mediante CGI. El CGI será recogido por un script Perl que interpretará los parámetros HTTP más significativos como: método, URI y argumentos de peticiones POST y PUT. Estos parámetros se usan por el módulo rutas para indexar con la funcionalidad de la API demandada. Una vez se realice la lógica necesaria mediante la librería, se escribirá en la salida estándar del programa el mensaje HTTP resultante, para que lo interprete el servidor Web y lo envíe al usuario. El script utilizado en este proyecto, es un script más reducido y adaptado, obtenido de un proyecto de software libre llamado Zevenet. El script corresponde con el fichero “/usr/local/zevenet/www/zapi/v3.1/zapi.cgi” que se encuentra en el repositorio. Script api.cgi Fichero: api.cgi #!/usr/bin/perl use WAF::HTTP; use CGI::Simple; my $cgi = CGI::Simple->new(); my $input_ref; if ($ENV{ CONTENT_TYPE } eq 'application/json' ) { my $data; $data = $cgi->param('POSTDATA') if $ENV{ REQUEST_METHOD } eq 'POST'; $data = $cgi->param('PUTDATA') if $ENV{ REQUEST_METHOD } eq 'PUT'; 34 require JSON::XS; JSON::XS->import; $input_ref = eval { decode_json( $data ) }; } ##### Load API routes require WAF::Routes; my $desc = 'Request not found'; my $req = $ENV{ PATH_INFO }; &httpResponse( {code => 404, desc => $desc, msg => "$desc: $req"} ); El script se apoya en una función encargada de adaptar la respuesta a un formato CGI correcto. sub httpResponse { my $self = shift; # Headers included in the responses, any method, any URI, # success or error my @headers = ( 'Access-Control-Allow-Origin' => "http://$ENV{ HTTP_HOST }/", 'Access-Control-Allow-Credentials' => 'true', 'Cache-Control' => 'no-cache', 'Expires' => '-1', 'Pragma' => 'no-cache', ); # header my $content_type = 'application/json'; if $self->{ type } && $self->{ body } { $content_type = $self->{ type }; } my $output = $q->header( -type => $content_type, -charset => 'utf-8', -status => "$self->{ code } $http_status_codes{ $self->{ code } }", # extra headers @headers, ); # body if ( exists $self->{ body } ) { if ( ref $self->{ body } eq 'HASH' ) { 35 require JSON::XS; JSON::XS->import; my $json = JSON::XS->new->utf8->pretty( 1 ); my $json_canonical = 1; $json->canonical( [$json_canonical] ); $output .= $json->encode( $self->{ body } ); } else { $output .= $self->{ body }; } } print $output; exit; } 1; Para ejecutar este script se necesitan dos librerías de Perl extras:  Libcgi-simple-perl, para el manejo de los CGI.  Libjson-xs-perl, para el manejo de JSON. Una vez creado el script es necesario aplicarle permisos de ejecución: $ sudo chmod +x /opt/waf/api/api.cgi 5.1.3 Rutas Buscando la escalabilidad de la aplicación y poder integrar la API con otros sistemas y futuros desarrollos. Se ha creado un módulo de enrutado, Routes.pm, que mediante las URIs y los métodos HTTP pueda seleccionar el módulo sobre el que actuar y la acción a realizar en él. Aquí se sigue una metodología REST, esta metodología utiliza los distintos verbos HTTP (POST, GET, PUT y DELETE) para aplicar acciones sobre objetos que se identifican mediante la URL. Así por ejemplo, para borrar un conjunto de reglas WAF debe ejecutarse una petición HTTP como la siguiente: DELETE http://127.0.0.1/waf/set-ejemplo Para listar las reglas de un set debe utilizarse una llamada como: GET http://127.0.0.1/waf/set-ejemplo El objeto set-ejemplo corresponde con el nombre del objeto sobre el que estamos actuando. Para extraer los identificadores de objetos set o rule se utilizarán expresiones regulares. Una vez identificada la 36 acción y el objeto sobre el que se va actuar, se enrutará con su modelo correspondiente. Enrutado de peticiones API Fichero: Routes.pm # $cgi is a object with the cgi variables # they are got by the Web server if ( $cgi->path_info =~ qr{^/waf} ) { require API::Waf; # define regular expression my $set_re = '[\w-]+'; # Get a WAF set if ( $cgi->{method} eq 'GET' && $cgi->{path} =~ '/waf/($set_re)$' ) { &retrieve_set( $cgi->{params}, $1 ); } # Create a WAF set if ( $cgi->{method} eq 'POST' && $cgi->{path} =~ '/waf$' ) { &create_set( $cgi->{params} ); } 5.1.4 Modelo El modelo controla la entrada y salida de datos de la API. Es una etapa previa al tratamiento de los datos, en la que se validan las entradas y se comprueban los posibles conflictos que pueden existir entre parámetros. En la salida, se estandarizan las respuestas, añadiendo códigos HTTP con los que identificar el resultado de la petición, y se estandarizan los formatos de datos propios de cada parámetro del objeto. Las comprobaciones que se aplican antes de ejecutar una acción son:  Si el objeto sobre el que se va a aplicar la acción existe.  Si todos los parámetros requeridos han sido enviados.  Si algún parámetro no es correcto para la llamada que se está ejecutando.  Si el valor de algún parámetro no es correcto.  Si existe ya un objeto antes de crear un objeto con ese nombre.  Si ya se encuentra aplicado un set a una granja, antes de aplicarlo. Modelo de una petición API Fichero: Set.pm, función: create_waf_set() # POST /waf 37 sub create_waf_set { my $json_obj = shift; require 'WAF::Config'; my $desc = "Create the WAF set, $json_obj->{ 'name' }"; # Check if it exists if ( &existWAFSet( $json_obj->{ 'name' } ) ) { my $msg = "$json_obj->{ 'name' } already exists."; return &httpErrorResponse( code => 400, desc => $desc, msg => $msg ); } my $params = { "name" => { 'valid_format' => 'waf_set_name', 'non_blank' => 'true', 'required' => 'true', }, "copy_from" => { 'valid_format' => 'waf_set_name', 'non_blank' => 'true', }, }; # Check allowed parameters my $error_msg = &checkAPIParams( $json_obj, $params ); return &httpErrorResponse( code => 400, desc => $desc, msg => $error_msg ) if ( $error_msg ); # executing the action if ( exists $json_obj->{ 'copy_from' } ) { unless ( &existWAFSet( $json_obj->{ 'copy_from' } ) ) { my $msg = "$json_obj->{ 'copy_from' } does not exist."; return &httpErrorResponse( code => 404, desc => $desc, msg => $msg ); } ©WAFSet( $json_obj->{ 'name' }, $json_obj->{ 'copy_from' } ); } else { &createWAFSet( $json_obj->{ 'name' } ); } my $output = &getZapiWAFSet( $json_obj->{ 'name' } ); # check result and return success or failure if ( $output ) { my $msg = "Added the WAF set $json_obj->{ 'name' }"; 44 Las funciones parse se utilizarán para las lecturas de los ficheros de configuración, mientras que las build se usarán para escrituras. Las funciones parse obtienen los parámetros de las reglas y sets por medio de expresiones regulares, aplicando una expresión por cada parámetro que se desea buscar. La función parse_set, responsable de formar el objeto set a través de las directivas SecLang de un fichero, se apoya principalmente en la función parse_rule. Esta función es una función recursiva, que se llamará a sí misma para autocontener cada uno de los chains que penden de la regla principal. El esquema que cumple es el siguiente: Parse_set() Parse_batch() Agrupar reglas anidadas Parse_rule() Añadir ID a cada regla Parse_conf Figura 14. Esquema para obtener un objeto set de reglas 45 Figura 15. Esquema de la función "parse_rule" 46 A su vez parse_rule utiliza las funciones parse_operator y parse_variables para obtener los parámetros exclusivos de las reglas match-action: Parse_rule my $rule; if ( $line =~ /^\s*SecRule\s+"?([^"]+)"?\s+"?([^"]+)"?\s+"?([^"]+)?"?/s ) { my $var = $1; my $ope = $2; my $act = $3; &parseWAFVariables($rule, $var); &parseWAFOperator($rule, $ope); &parseWAFAction($rule, $act); } elsif ( $line =~ /^\s*SecAction\s+"?([^"]+)?"?/s ) { my $act = $3; &parseWAFAction($rule, $act); } Parse_variables sub getWAFVariables { my $rule = shift; my $var = shift; my @var_sp = split ( '\|', $var_reg ); $rule->{variables} = \@var_sp; } Parse_operator sub getWAFOperator { my $rule = shift; my $oper = shift; my $operator = ""; my $operating = ""; if ( $oper =~ /^(?<operator>!?\@\w+)?\s+?(?<operating>[^"]+)?$/ ) { $rule->{operator} = $+{ operator } // ""; $rule->{operating} = $+{ operating }; 47 } # set not_operator if ( $rule->{operator} ) { $rule->{operator} =~ s/^(!)?\@//; my $not_op = $1 // ''; $rule->{operator} = "${not_op}$rule->{operator}"; } } Parse_actions sub getWAFActions { my $rule = shift; my $act = shift; my @options = split ( ',', $act ); foreach my $param ( @options ) { $param =~ s/^\s*//; $param =~ s/\s*$//; if ( $param =~ /msg:'?([^']+)'?/ ) { $rule->{ description } = $1; } elsif ( $param =~ /id:'?([^']+)'?/ ) { $rule->{ rule_id } = $1; } elsif ( $param =~ /tag:'?([^']+)'?/ ) { push @{ $rule->{ tag } }, $1;} } # ... elsif ( $param =~ /^chain$/ ) { foreach my $ru ( @nested_rules ) { $rule->{ raw } .= "\n" . &convertWAFLine( $ru ); push @{ $rule->{ chain } }, &parseWAFRule( $ru ); $rule->{ chain }->[-1]->{ type } = 'match_action'; } } # ... Para transcribir un set en un fichero se utilizará la función build_set. La parte de escritura es más simple, build_conf crea las directivas correspondientes a la configuración y a continuación se llama a build_rule una vez por cada una de las reglas que hay definidas en la lista de reglas del set. Las reglas se escribirán usando 48 una línea por cada uno de los parámetros que la regla tenga configurados, añadiendo el carácter “\” al final de línea para mejorar la legibilidad. Antes de sobreescribir el set se comprobará que comprobará su sintaxis, asegurando que ninguna de las reglas está mal escrita o que existe alguna incompatibilidad que rompa el set. Para ello se utilizará la opción „-W‟ que se añadió a Pound para comprobar la sintaxis de las de un fichero de reglas WAF. Check_syntax sub checkWAFFileSyntax { my $file = shift; my $out = `pound -W $file 2>&1`; my $err = $?; if ( $err ) { chomp $out; #parse response and return field that failed my @aux = split ( '\n', $out ); $out = $aux[1]; $out =~ s/^.+Column: \d+. //; } else Build_conf Build_set Crea el socket de control Por cada regla del set: Build_rule Figura 16. Esquema de la función "build_set" 49 { $out = ""; } return $out; } Por último se recargarán los procesos de Pound que estuviesen utilizando dicho set de reglas. Aquí se utilizará la opción „-R‟ de Pound. Reload_set sub reloadWAFByFarm { my $farm = shift; my $err = 0; require WAF::Parser; my $socket = getFarmSocket( $farm ); my $set_file; # check set foreach my $set ( &listWAFByFarm( $farm ) ) { $set_file = &getWAFSetFile( $set ); return 1 if ( &checkWAFFileSyntax( $set_file ) ); } $err = system( "pound_ctl -c $socket -R" ); return $err; } A veces interesará modificar reglas SecLang sin utilizar los objetos que se han definido. Ya sea porque se requiera modificar una directiva o parámetro de los que no soportan estos objetos, o se quieran añadir reglas SecLang que se hayan obtenido de alguna fuente. Para ello se utiliza el parámetro raw definido en todas las reglas. En este parámetro puede ser enviada una o varias directivas. Sólo es necesario crear una cadena con las reglas y separarlas por un carácter de fin de línea „\n‟. Para tratar el campo raw se ha creado la función build_batch, que sustituye una regla ya existente o añade una nueva al set con el contenido de este campo. Una vez la regla ha sido añadida al set, éste se comprobará para comprobar que la nueva regla no rompe la coherencia del set. 5.4 Aplicar reglas al balanceador Un balanceador de carga define como granja a la agrupación de un conjunto de servidores, que se encuentran sirviendo el mismo servicio, bajo una misma interfaz virtual del balanceador. Para los ejemplos aquí mostrados, una granja corresponde a un proceso de Pound que arranca con una configuración y sirve un servicio a través de una IP y puerto virtuales. 50 Una vez haya configurado uno o varios sets será posible asociarlos con procesos de Pound con el objetivo de que el tráfico HTTP pueda ser analizado. En esta memoria se utilizará el directorio “/opt/farms“ para guardar los archivos de configuración de Pound, cada uno de ellos representa una granja de balanceo. Para trabajar la asociación de un set y una granja se añadirán tres llamadas para la API, las cuales implementarán las acciones: añadir un set a la granja, eliminar un set de una granja y cambiar la posición de un set dentro de una granja. Los esquemas para cada una de las acciones son: Para mover los sets se ha utilizado una de las propiedades que tiene Perl, los slices. Los slices es la forma que usa Perl de manejar listas, y es que Perl permite manipular las listas como si fuesen subconjuntos de datos, así de una forma ágil pueden reescribirse listas reordenándolas durante el proceso. sub moveByIndex { my ( $list, $ori_index, $dst_index ) = @_; my $elem = $list->[ $ori_index ]; # delete item splice (@{ $list }, $ori_index, 1); # add item splice (@{ $list }, $dst_index, 0, $elem); } 5.5 Resumen 5.5.1 Ficheros Tras el desarrollo de la API el árbol de ficheros de código y rutas de configuración es el siguiente: /opt/waf | |--api/ | | | |--api.cgi | |--config/ | |--Routes.pm | |--Model/ | | | |--Sets.pm | | | |--Rules.pm | | | |--Structs.pm 51 | |--lib/ | |--Actions.pm | |--Core.pm | |--Parser.pm | |--Config.pm /opt/farms Los directorios lib y Model y el fichero Routes.pm tienen un enlace simbólico en el directorio “/usr/share/perl5/WAF/” para poder ser utilizados como un módulo más de Perl. Los ficheros más relevantes del servidor Web son:  /etc/apache2/mods-enabled/cgi.load. Habilitar CGI  /etc/apache2/conf-available/serve-cgi-bin.conf. Configuración de CGI  /etc/init.d/apache2. Script de parada y arranque de apache 5.5.2 Objetos Los objetos definidos en el apartado 3.2, utilizados por la librería que gestiona el WAF, no corresponden estrictamente a los objetos que aparecen en la interfaz de la API. Esto es debido a que los campos menos usados se han ocultado y otros se han agrupado, con la finalidad de hacer una herramienta más simple de manejar. El objeto set contiene los siguientes campos: Identificador Tipo de dato Descripción Default_action String Establece una acción por defecto para el campo action de una regla. Default_log String Establece una acción por defecto para el campo log de una regla. Default_phase String Establece una acción por defecto para el campo phase de una regla. Audit String Habilita o deshabilita el log de auditoría. Process_request_body String Habilita o deshabilita examinar el body de las peticiones. Process_response_body String Habilita o deshabilita examinar el body de las respuestas. Request_body_limit Numérico Establece un tamaño máximo para el body de la petición. 52 Status String Habilita o deshabilita el set. También puede establecerse el modo solo detección. Disable_rules Lista de números Deshabilita las reglas especificadas en la lista. Se utiliza el campo rule_id de la regla. Tabla 6. Campos de configuración de un set API El objeto regla contiene los siguientes campos: (los códigos que corresponden a la segunda columna “Reglas que lo soportan” son ; c: custom, m: mark, a: action, -: match-action y x: chain). (los códigos que corresponden a la tercera columna “Tipo de campo” son ; d: descriptivo, a: acción o m: match). Identificador Reglas que lo soportan Tipo de campo Descripción c m a - x d m a Id X X X X X Posición de la regla dentro del set Type X X X X X Tipo de regla: mark, custom, action o matchaction Raw X X X X Regla en formato SecLang. Tal como se encuentra en el fichero de configuración Mark X X Nombre con el que referenciar la marca. Variables X X X Variables para analizar con el operador. Vienen definidas en la documentación de Libmodsecurity Operator X X X Acción a aplicar para buscar patrones en cada una de las variables. Vienen expecificadas en la documentación de Libmodsecurity. Operating X X X Es el valor que se le aplica al operador para buscar en las variables. Rule_id X X X Identificador único para una regla. Description X X X Mensaje descriptivo. Tag X X X Etiquetas para clasificar las reglas. Severity X X X Nivel de criticidad de la regla. Phase X X X Fase de libmodsecurity en la que se aplicará la regla. Transformations X X X X Transformaciones que se le aplican a la variable antes de examinar un patrón. 53 Multi_match X X X X Examina el patrón para cada una de las transformaciones. Capture X X X X Captura datos de expresiones regulares. Action X X X Acción a aplicar cuando el patrón detecte una coincidencia. Http_code X X X Código HTTP para las acciones redirect o deny Execute X X X X Ruta a un script LUA que se ejecutará. Log X X X Escribe en el log en caso de que la regla se cumpla. Audit X X X Fuerza que la regla no se escriba en el log de auditoría. Set_variable X X X X Aplica una acción de modificar variable, por cada directiva setvar que aparezca en la regla SecLang. Chain X X Son reglas encadenadas, que se ejecutarán de forma secuencial. Skip X X X Salta un número de reglas. Skip_after X X X Salta un número de reglas o hasta una marca. Tabla 7. Tabla de parámetros para cada tipo de regla 5.5.3 Acciones Los parámetros de la URI que aparecen entre los caracteres „<‟ y „>‟ identifican a objetos que deben haber sido creados y sobre los que se realizará la acción. Método URI Descripción GET /waf Lista los sets disponibles. POST /waf Crea un nuevo set. GET /waf/<set> Detalla los parámetros de un set PUT /waf/<set> Modifica la configuración global del set. DELETE /waf/<set> Elimina un set. POST /waf/<set>/rules Crea una nueva regla en un set. GET /waf/<set>/rules/<rule> Detalla una regla determinada de un set. PUT /waf/<set>/rules/<rule> Modifica los parámetros de una regla. 60 500 directivas - resp head 2091 1.74% 2200 4.87% 0.95 600 directivas - req head 4042 3.37% 4225 9.35% 0.95 Tabla 12. Resultados de pruebas de rendimiento Los cuadros rojos indican que la CPU está cerca de saturar, por encima del 90%. Las columnas de Pound y Nginx se subdividen en dos columnas a su vez. La primera es el número de peticiones HTTP por segundo que el sistema ha procesado. La segunda es la eficiencia del sistema con respecto al primer caso, el sistema sin procesar WAF. La última columna refleja una comparativa del rendimiento entre la implementación hecha con Pound y la hecha con Nginx. Figura 18 . Gráfico de peticiones respecto al número de directivas analizadas 6.2 Tests de la API Debido a que el proyecto expuesto a lo largo de esta memoria consiste en un desarrollo software, se ha querido dotar a la implementación de unos tests funcionales que aseguren la integración continua del código en futuros desarrollos y faciliten su depuración. Estos tests consistirán en peticiones HTTP en las cuales se pruebe la API de configuración, se examine que las respuestas sean coherentes y el estado de los procesos sea el correcto. Cada test será recursivo, esto quiere decir que una vez finalizado el test, el sistema quedará en el mismo estado en el que se encontraba antes de probarlo. Se han definido dos tests, cada uno de ellos escritos en Perl.  En un primer test se comprobará la configuración, se lanzarán todas las llamadas creadas en el punto 3 y se probará cada uno de los parámetros de cada tipo de objetos WAF.  En un segundo test se creará un sistema de balanceo funcional, se crearán reglas de protección y se realizarán peticiones HTTP contra el balanceador, comprobando tanto peticiones que deben ser bloqueadas como que deben ser permitidas. 0 20 40 60 80 100 120 Peticiones atendidas (en millares) Pound Nginx 61 El árbol en el cual se organizan los ficheros pertenecientes a los tests siguen el siguiente esquema: | |--tests/ | | | |--waf_config.t | | | |--waf_requests.t | |--Includes/ | | | |--Objets/ | | | | | |--waf.pm | | | |--Api/ | | | |--waf.pm | |--Functions.pm | |--Config.ini 6.2.1 Configuración El fichero Config.ini contiene la configuración relativa al balanceador de carga. Esta información es usada por las funciones de test tanto para lanzar peticiones como para conectarse por SSH y comprobar cosas en el sistema. IP=192.168.100.150 pass=admin1admin 6.2.2 Definiciones En el directorio includes aparecen las definiciones de objetos importados por los tests. Se han creado dos tipos de includes:  Los ficheros del directorio objects son definiciones de objetos reglas y sets, cada parámetro del objeto tiene valores establecido. El sentido de este fichero es crear objetos funcionales y útiles que puedan ser importados en cualquier test, modularizando la aplicación y facilitando su uso. Objects/waf.pm Sub obj_waf_set_config { return { 'audit' => 'true', 'process_request_body' => 'true', 'process_response_body' => 'true', 'request_body_limit' => '6456456', 'disable_rules' => [100,102], 62 'default_action' => 'pass', 'default_log' => 'true', 'default_phase' => 2, 'status' => 'detection', }; }  Los ficheros del directorio api son las definiciones de las llamadas a la API. Aquí se guardan estructuras con los campos necesarios para hacer cada una de las llamadas a la API. Por ejemplo que verbo usa, a que URI hay que efectuar la llamada o qué parámetros está esperando. Es posible cargarlas usando objetos reglas y set, en ese caso la llamada API se parametrizaría con los datos necesarios para crear, modificar… ese objetos en concreto. Api/waf.pm sub list_set { return { 'method' => 'GET', 'uri' => "/waf", }; } # create_waf ( $set_obj ) sub create_waf { my $params = shift; my $set = $params->{ name }; return { 'method' => 'POST', 'uri' => "/waf", 'params' => { 'name' => $set, }, 'remote_checks' => [ { "cmd" => "ls $WAF_PATH/${set}.conf", }, ], }; } 6.2.3 Funciones Toda la lógica usada en los tests está en el fichero Functions.pm. La función más relevante es la que ejecuta cada una de las sentencias test, que utiliza el módulo Test::More para procesar la resolución. La función test espera una lista de acciones, las cuales ejecutará y comprobará su salida de error. La función 63 test a su vez se apoya en 3 funciones, cada una de ellas ejecuta un tipo diferente de comprobación. La función Api ejecuta una llamada a la API utilizando los parámetros de los campos „method‟, „uri‟ y „params‟. Una vez ejecutada la llamada se comprueba el código de error, aceptándose 2xx como éxito. Posteriormente la función test, quién lanza api, devuelve el objeto JSON respondido por la API, por si se quisiesen hacer comprobaciones extra sobre los parámetros devueltos por la aplicación. Parámetros de entrada para la función api. { 'method' => 'PUT', 'uri' => "/set/$name", 'params' => { 'ip' => $ip, 'netmask' => $netmask, 'gateway' => $gateway, }, } La función api realiza la petición HTTP usando el cliente de la librería LWP::UserAgent. La creación de la petición se compone de las siguientes etapas:  Inicialización de la interfaz por la que se comunica con el servidor Web: $ua = LWP::UserAgent->new( agent => '', ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0x00 } );  Formulación de la petición. my $request = getRequest( $method, $uri );  Si la petición es POST o PUT, se crea el body del mensaje, añadiéndose los argumentos y la cabecera content-type correspondiente. Los parámetros serán codificados en formato JSON con el módulo de JSON. if ( $method eq 'POST' or $method eq 'PUT' ) { $request->content_type( 'application/json' ); $request->content( JSON::encode_json( $params ) ); } 64  Una vez creada la petición, se ejecuta y se obtiene la salida. my $response = $ua->request( $request );  Por último se devuelve el código HTTP resultante de la petición y el objeto JSON con los argumentos de respuesta decodificados. eval { $json_dec = JSON::decode_json( $response->content() ); }; return $out = { 'code' => $response->code, 'Params' => $json_dec, }; Durante el test también pueden ejecutar comandos en el nodo remoto, el balanceador de carga sobre el que se están realizando las pruebas, con la intención de conocer el estado del sistema. Ésto es útil para saber si las acciones se están aplicando correctamente y si el estado de los procesos es el deseado. Las comprobaciones remotas son comandos linux que se ejecutan mediante SSH. Estas comprobaciones tienen dos parámetros, el primero es el comando que se desea ejecutar, y el segundo es el resultado esperado tras la ejecución. Definición de una comprobación remota: 'remote_checks' = [ { "cmd" : "groups user | grep rbac", "expect_err" : "true", } ]; Ejecución y comprobación del comando: $cmd_out = system("ssh root\@$HOST $cmd" ); if ( $cmd_out and $expect_err eq 'false' ) { $error += 1; } elsif ( !$cmd_out and $expect_err eq 'true' ) { $error += 1; } 65 Los comandos locales son ejecutados en la máquina cliente, la que ejecuta los tests contra el balanceador de carga. El interés de estas comprobaciones es lanzar aplicaciones de cliente para extraer datos como: el rendimiento o el estado de un servicio. Al igual que en las comprobaciones remotas los parámetros usados son: el comando a ejecutar, y si se espera algún error de la ejecución. Definición de una comprobación local: 'local_checks' = [ { "cmd" => "groups user | grep rbac", "expect_err" : "true", } ]; Ejecución y comprobación del comando: $cmd_out = system( $cmd ); if ( $cmd_out and $expect_err eq 'false' ) { $error += 1; } elsif ( !$cmd_out and $expect_err eq 'true' ) { $error += 1; } 6.2.4 Tests Los ficheros tests son scripts en Perl, se encuentran en el directorio tests y están identificados por la extensión .t. Son pruebas que se ejecutan de forma secuencial y en las que se aplican acciones sobre el balanceador de carga para posteriormente comprobar su efecto. Cada una de las pruebas es una ejecución de la función test ya antes detallada. Las partes de las que se compone un tests son:  Carga dependencias y variables de configuración: #!/usr/bin/perl use Config::Tiny; require Functions; # VARIABLES my $Config = Config::Tiny->read( 'config.ini' ); my $HOST = $Config->{ _ }{ HOST }; 66  Carga de objetos: # define objects my $mark = &obj_waf_mark(); my $rule1 = &obj_waf_rule_all(); my $rule2 = &obj_waf_rule_all_2(); my $set_conf = &obj_waf_set_conf_all(); my $setname1 = 'test_waf_ori'; my $test_def; my $error = 0; my $api;  Carga de llamada API y ejecución: # Creating a set $test_def = &create_waf_rule( $rule1, [$setname1] ); $error += &test( $test_def ); # creating a rule $test_def = &custom_waf_rule( $custom, [$setname1] ); $error += &test( $test_def ); # showing a rule $test_def = &show_waf_rule( [$setname1, 0] ); $error += &test( $test_def ); # copying a rule $test_def = ©_waf_rule( { copy_from => $rule1->{ rule_id } }, [$setname1] ); $error += &test( $test_def ); Para validar el desarrollo API se han creado dos test:  Test de configuración. Ejecuta todas las llamadas de la API y configura un sistema WAF completo, evaluando gran cantidad de opciones.  Test funcional. Crea un sistema funcional, configurando una granja de balanceo, añadiéndole un backend y aplicándole una configuración de seguridad. Tras esto ejecuta peticiones HTTP y se comprueba que sean bloqueadas. 6.2.5 Ejecución Para ejecutar los tests, se utilizará el la herramienta prove de Linux ya que lo test han sido integrados con el módulo Test::More que se integra con la herramienta. cd tests prove -I. waf_conf.t 67 Figura 19. Resultado del test de configuración prove -I. waf_requests.t 68 Figura 20. Resultado del test funcional de sistema 6.3 Usos prácticos En este punto se realizarán peticiones HTTP que simulan algunos de los ataques a Webs más comunes. El tráfico circulará a través del balanceador de carga, que será el encargado de examinarlo y bloquear aquellas peticiones que intenten aprovecharse de alguna de las vulnerabilidades. Las reglas WAF se aplicarán mediante la API, utilizando la interfaz definida en el capítulo 3 de la memoria. 6.3.1 Escenario de pruebas HOST IP Puerto Admin 192.168. - CL 192.168.1.186 - LB 192.168.102.241 8080 API 192.168.102.241 80 BK 192.168.100.127 80 Tabla 13. Hosts del escenario “usos prácticos” 69 El backend mostrado en el escenario devuelve una página con un simple mensaje “servidor alcanzado” y un código 200 con lo que comprobar si la petición ha sido respondida por el backend. El balanceador de carga, bloqueará la petición y devolverá un código 403 al cliente en caso de que alguna regla WAF aplique. La configuración con la que Pound arranca es la siguiente: Name lb_granja ThreadModel dynamic Control “/tmp/lb_control.socket” ListenHTTP Address 192.168.102.241 Port 8080 Service “servers” BackEnd Address 1192.168.100.127 Port 80 End End End Para hacer funcionar estas pruebas es importante que Libmodsecurity haya compilado con la librería YAJL (encargada de parser objetos JSON), ya que se utilizarán objetos JSON para enviar datos en las peticiones POST. También hacen falta algunas reglas que nos aseguren que los JSON son decodificados, estas reglas se encuentran en el set de reglas recomendadas por los desarrolladores de Libmodsecurity. Por ello se copiará dicho set en el path usado para las reglas WAF, una vez copiado, la API podrá trabajar con él como si hubiese sido creado por un usuario. El fichero de reglas recomendadas puede encontrarse dentro del directorio principal del proyecto. /index.html Figura 21. Escenario para usos prácticos CL LB BK WAF Admin API 76 --data '{ "username": "admin", "password":"\") or true--" }' Respuesta HTTP Replied forbidden Error code: 403 6.3.5 DoS Algunas veces una aplicación contiene llamadas pesadas que pueden bloquear el servidor Web. Con el WAF se puede limitar el número de llamadas a un recurso. Para ello se utilizarán tres reglas con la siguiente secuencia:  Registrar el recurso al que se está accediendo  Bloquear si el contador ya ha llegado al límite.  En caso de que no haya llegado, incrementar el contador. Debido a que algunas de las propiedades necesarias para estas reglas no han sido implementadas en la API, se formularán las reglas a partir de la opción raw. Objeto Json enviado. { "raw" : [ "SecAction \"phase:1,initcol:resource=%{REQUEST_URI},pass,nolog\"", "SecRule RESOURCE:COUNTER \"@gt 2\" \\", "\"deny,phase:1,id:7\"", "SecRule REQUEST_URI \"limited_resource\" \\", "\" phase:1,setvar:resource.counter=+1,pass,id:9\"" ] } Regla SecLang del fichero de configuración. SecAction "phase:1,initcol:resource=%{REQUEST_URI},pass,nolog" # SecRule RESOURCE:COUNTER "@gt 2" \ "deny,phase:1,id:7" # 77 SecRule REQUEST_URI "limited_resource" \ "phase:1,setvar:resource.counter=+1,pass,id:9" # La regla añadida bloqueará el recurso tras 3 consultas exitosas. Petición HTTP contra el backend curl --request GET \ --url http://192.168.102.241:80/limited_resource.html Respuestas HTTP Try 1: [OK] Web alcanzada Try 2: [OK] Web alcanzada Try 3: [OK] Web alcanzada Try 4: Replied forbidden 6.3.6 Ataques de fuerza bruta Para proteger frente a ataques de fuerza bruta se combinarán técnicas ya vistas como el contador de intentos visto en DoS, la comprobación de parámetros de entrada y examinar el código de respuesta enviado por el servidor para saber el logging resultó fallido. La regla que se aplicará bloqueará las peticiones de un cliente tras 3 intentos de contraseña incorrectos. Objeto Json enviado. { "raw" : [ "SecAction \"initcol:ip=%{REMOTE_HOST},pass,nolog\"", "SecRule IP:COUNTER \"@gt 2\" \\", "\"deny,phase:4,id:7\"", "SecRule ARGS_NAMES.json \"password\" \\", "\"id:9,chain\"", "SecRule RESPONSE_STATUS \"^[45]\" \\", "\"phase:4,setvar:ip.counter=+1,pass\"" ] } Regla SecLang del fichero de configuración. SecAction "initcol:ip=%{REMOTE_HOST},pass,nolog" SecRule IP:COUNTER "@gt 2" \ "deny,phase:4,id:7" 78 SecRule ARGS_NAMES.json "password" \ "id:9,chain" SecRule RESPONSE_STATUS "^[45]" \ "phase:4,setvar:ip.counter=+1,pass" Petición HTTP contra el backend curl --request POST \ --url http://192.168.102.241:80/ \ --header 'content-type: application/json' \ --data '{ "username": "admin", "password":"pas1234" }' Respuestas HTTP Try 1: [OK] Web alcanzada Try 2: [OK] Web alcanzada Try 3: [OK] Web alcanzada Try 4: Replied forbidden 79 80 7 CONCLUSIONES Y FUTUROS DESARROLLOS “Nunca te das cuenta de lo que has logrado; sólo puedes ver lo que queda por hacer.” - Marie Curie Tras la etapa de validación se analizará el rendimiento y la gestión del WAF obteniendo algunas conclusiones. También, se dará una visión de cómo podría crecer el proyecto y qué futuros desarrollos sería posible acometer. 7.1 Conclusiones El resultado del proyecto es un sistema totalmente funcional, formado por un balanceador de cargas, que inspecciona datos de las aplicaciones que virtualiza, y que es capaz de permitir o denegar peticiones en función de las reglas con las que se le configure. Las reglas de seguridad del balanceador son configuradas a través de una API de gestión que se ha creado para dicha tarea. La API abstrae al operador de seguridad de la compleja sintaxis de reglas que utiliza la librería y permite crear objetos que busquen contenido potencialmente malicioso en los campos deseados de una trama HTTP. La API es una aplicación independiente que ha sido montada sobre un servidor Web en el mismo host que el balanceador y que construye reglas complejas a través de objetos simplificados. Desde el punto de vista del rendimiento, y analizando los datos del apartado 6.1 puede verse que se ha obtenido un rendimiento similar al de otro producto ya en el mercado, alcanzando como máximo una penalización del 5% cuando el sistema se encuentra saturado. Con esto se entiende que el cuello de botella en la tecnología es la librería, que debido a la cantidad de comprobaciones por paquete que se debe hacer, se eleva el consumo de CPU. Para aumentar el número de peticiones por segundo sería necesario aumentar la CPU de la máquina, por lo que en entornos industriales, con mejores hardwares, se podrían conseguir mejores benchmarks. De aquí se puede sacar un claro ejemplo de que la seguridad no es gratis, requiere de recursos de computación que al final repercute en equipos más caros. El análisis de rendimiento se utilizó para saber los puntos de mejora del proyecto aunque desde un primer momento la solución no está enfocadas para centros de alto procesamiento. Es una solución compacta de balanceador de carga más capa de seguridad pensada para entornos como los de mediana empresa. Con respecto a la gestión la herramienta API, creada para la gestión del WAF, es muy flexible, incluso puede paliar algunos de los ataques a servidores Web más comunes sólo filtrando el dato HTTP que se desea analizar. Aunque requiera de cierta familiarización con el lenguaje para construir reglas más complejas, se permite la importación de reglas en formato SecLang, haciendo la API totalmente compatible. Para aquellos entornos con fuertes directivas de seguridad, la API permite abstraer al operador de seguridad de la infraestructura, no necesitando acceso a la máquina para poder gestionar la seguridad, y quedando los distintos entornos aislado. Es importante recordar que las reglas requieren un mantenimiento, y actualizaciones en caso de que la aplicación cambie o aparezcan nuevas vulnerabilidades. La eficiencia de dichas reglas dependerá de los conocimientos del operador a la hora de construir reglas y del número de reglas que se examinen durante el análisis del tráfico. Siempre existiría la posibilidad de adquirir reglas de proveedores privados que ya se encargan de optimizar su funcionamiento y su precisión. 81 7.2 Futuros desarrollos Tras finalizar el proyecto quedan abiertas nuevas tareas con las que dotarlo de nuevas propiedades y complementarlo con herramientas que lo hagan más atractivo de cara al usuario. La siguiente etapa de desarrollo podría consistir en un interfaz gráfico, que de forma intuitiva facilite al usuario la creación de reglas sin recurrir directamente a la API. El entorno gráfico utilizaría la API como interfaz de comunicación con el WAF, presentando las tareas de mantenimiento sobre un formulario en el que se muestren los posibles valores para cada uno de los campos. Para crear el interfaz gráfico se podrían usar los modernos frameworks de desarrollo Web como Angular o React ya bien acogidos por la comunidad de desarrolladores. Tras implementar la parte gráfica, el proyecto tendría una estructura: vista (interfaz gráfica), modelo (API) y controlador (librería). También una de las tendencias tecnológicas más de moda es la inteligencia artificial y machine learning. Por ello se podría crear una aplicación que de forma autónoma cree reglas optimizadas a la aplicación que se está protegiendo. Para ello habría que crear una buena base de datos de vectores de ataques y peticiones legítimas con los que alimentar la aplicación de autoaprendizaje. Ya que hay empresas que comercializan paquetes de reglas, se podría crear un sistema de actualizaciones que de forma autónoma compruebe la disponibilidad y gestione la actualización de dichos paquetes. Por último ya que el punto de mejora más evidente es el consumo de CPU de la librería, habría que buscar puntos de mejora, hacer análisis de performance y optimizar su rendimiento utilizando técnicas como zero copy y buscando alternativas en librerías de parseo de contenido y pipeline. 82 83 APÉNDICES Instalación de Libmodsecurity Para la instalación de Libmodsecurity se han ejecutado los pasos detallados por su desarrollador: 1. Lo primero será instalar sus dependencias. La implementación de este proyecto se basa en Debian, por lo que se instalarán los paquetes desde el repositorio: apt-get update && apt-get install -y automake bison build-essential \ g++ gcc libbison-dev libcurl4-openssl-dev libfl-dev libgeoip-dev \ liblmdb-dev libpcre3-dev libtool libxml2-dev libyajl-dev make \ pkg-config zlib1g-dev 2. Una vez instaladas las dependencias se obtiene el proyecto de su repositorio GIT y se actualiza para tenerlos últimos cambios: git clone --depth 1 -b v3/master \ --single-branch https://github.com/SpiderLabs/ModSecurity cd ModSecurity/ git submodule init git submodule update 3. Por último construimos los binarios y los instalamos en una ruta del sistema ./build.sh && ./configure –prefix=/usr/local/modsecurity \ && make && make install Diagrama de Gantt La primera etapa del proyecto ha consistido en analizar las soluciones disponibles, el estado del arte. En esta etapa se han montado pequeñas demos con las que familiarizarse con las soluciones y ver las capacidades que cada una de ellas ofrecía. Una vez elegida una solución se ha comenzado con la documentación. Esta etapa ha sido siempre una tarea paralela al desarrollo, con la intención de dejar constancia de las decisiones de diseño que se iban tomando. Una vez terminada la integración entre Pound y Libmodsecurity se ha hecho un test de rendimiento, con el que validar si el desarrollo cumplía con lo esperado. Tras ser aprobada la integración, se comenzó con la implementación de la API. Para evitar arrastrar errores e introducir bugs en propiedades que ya habían sido testeadas, se llevó en paralelo el desarrollo de la aplicación de tests. Esta tarea ha hecho posible desarrollar de forma confiada, ya que continuamente se ha ido probando, el nuevo código y lo ya implementado hasta el momento. Por último se han realizado pruebas con casos prácticos con las que mostrar la potencia de la solución y sus posibles usos. 84 Figura 23 . Diagrama de Gannt 13-jul. 2-ago. 22-ago. 11-sep. 1-oct. 21-oct. 10-nov. Estado del arte Integración Pound y Libmodsecurity Test de rendimiento Implementación de la API Test pruebas API Validación Documentación Proyecto 85 4 int parse_headers( const char *header, char **key, \ int *key_size,char **value, int *value_size ) { int fin = 0; int flag_clr=0; int parsing_value=0; int i; *key = header; *key_size = 0; *value_size = 0; if (header == NULL) return -1; // look for the separator, ':' for ( i=0; i<MAXBUF && !fin; i++ ) { if (!parsing_value) { // end key if (header[i] == ':') fin = 1; else if (header[i] == '\0') fin = -1; //wrong parse } } if (fin == 1) { i--; *key_size = i; *value=header+i+2; *value_size = strlen(header)-2-*key_size; // rest size of " : } return fin; } Waf_add_http_info Toma la primera línea de la cabecera enviada por el cliente y parsea los datos: verbo, URI y versión HTTP. Tras eso pasa la información a la librería. int waf_add_http_info(Transaction *t, const char *header) { int ret = 0; char version[5]; char verb[20]; char uri[512]; int i = 0; int str_ind=0; int vers_flag= 0; int size = strlen(header); int fin; 5 // parse verb for (fin=0,str_ind=0;!fin && i<size; i++, str_ind++) { if ( header[i] == ' ' ) { verb[str_ind] = '\0'; fin=1; } else verb[str_ind] = header[i]; } // parse path for (fin=0,str_ind=0;!fin && i<size; i++, str_ind++) { if ( header[i] == ' ' ) { uri[str_ind] = '\0'; fin=1; } else uri[str_ind] = header[i]; } // parse version for (fin=0,str_ind=0;!fin && i<size; i++) { if (header[i] == '\0') fin=1; else { if ( vers_flag ) { version[str_ind]=header[i]; str_ind++; } else if (!vers_flag && header[i] == '/') vers_flag=1; } } msc_process_uri(t, uri, verb, version); return ret; } Waf_add_req_head Toma la petición del cliente y extrae la línea con los campos relativos al verbo, URI y versión y las cabeceras. Tras ello manda los datos a las funciones que se encargan de su procesamiento waf_add_http_info y parse_headers. int waf_add_req_head(Transaction *t,char const **headers,int num_headers) { char *key; int key_size; char *value; 6 int value_size; int ret = 1; int cont=1; waf_add_http_info(t, headers[0]); // skip first header, it is the VERB, URI and VERSION for(int i = 1; cont == 1 && i<num_headers; i++) { cont = parse_headers( headers[i], &key, &key_size, &value, \ &value_size); if ( cont == 1 ) msc_add_n_request_header(t, key, key_size, value, value_size); else { ret = 0; } } msc_process_request_headers(t); return ret; } Waf_add_resp_head Toma las cabeceras de respuesta del servidor, extrae el código HTTP y manda las cabeceras a la Libmodsecurity a través de la función parse_headers. int waf_add_resp_head(Transaction *t, char const **headers, int \ num_headers) { char *key; int key_size; char *value; int value_size; int ret = 1; int cont=1; int http_code; char http_code_str[4]; char http_version[9]; char const *p; // parse http response code // parse version p=headers[0]; int size = strlen(p); int param=0; char aux; for (int i=0, ic=0; param<2 && i<size; i++, ic++) { aux=p[i]; if ( aux == ' ' ) aux = '\0'; 7 if (param == 0) http_version[ic]=aux; else if (param == 1) http_code_str[ic]=aux; if ( aux == '\0' ) { ic=-1; param++; } } http_code=atoi(http_code_str); // parse response headers for(int i = 1; cont == 1 && i<num_headers; i++) { cont = parse_headers( headers[i], &key, &key_size, &value, \ &value_size); if ( cont == 1 ) msc_add_n_response_header(t, key, key_size, value, \ value_size); else { ret = 0; } } msc_process_response_headers(t, http_code, http_version); return ret; } Read_body Lee el socket de cliente o servidor y toma el body. El body es guardado en un buffer para posteriormente analizarlo y reenviarlo a su destinatario original. Esta función es ejecutada tras haber obtenido todas las cabeceras. int read_body(BIO *sock, char **buff, int size) { if (*buff) logmsg(LOG_ERR, "(%lx) body buffer is busy", pthread_self()); else { *buff=(char *)malloc(size*sizeof(char)); while(BIO_read(sock, *buff, size) < size){} } } Waf_resolution Esta función comprueba el estado de Libmodsecurity, debe ejecutarse después de que ésta haya analizado 8 algún dato, para ver cuál ha sido su resolución. int waf_resolution(Transaction *t,int *int_code, char **url) { ModSecurityIntervention intervention; intervention.status = 200; intervention.url = NULL; intervention.log = NULL; intervention.disruptive = 0; WAF_ACTION waf_action = ALLOW; if (msc_intervention(t,&intervention)) { msc_process_logging(t); if (intervention.url) { waf_action = REDIRECTION; if (intervention.status == 200) intervention.status = 302; // default value } else if (intervention.disruptive) { waf_action = BLOCK; if (intervention.status == 200) intervention.status = 403; // default value } } return waf_action; } 9 10 ANEXO B: EJEMPLOS LLAMADAS API El contenido de este anexo es un ejemplo de petición-respuesta para cada una de las llamadas de la API. Las reglas creadas en estas pruebas no tienen validez funcional, son solo ejemplos para comprobar el correcto funcionamiento de la gestión ejercida por la API. Creación de un set >> Petición: curl -X POST -H 'Content-Type: application/json' -d '{"name":"test_waf_ori"}' http://192.168.100.150:80/api.cgi/waf << Respuesta: { "description" : "Create the WAF set, test_waf_ori", "message" : "Added the WAF set test_waf_ori", "params" : { "configuration" : { "audit" : "false", "default_action" : "allow", "default_log" : "", "default_phase" : "1", "disable_rules" : [], "process_request_body" : "false", "process_response_body" : "false", "request_body_limit" : "", "status" : "false" }, "rules" : [] } } Crear una regla >> Petición: curl -X -H 'Content-Type: application/json' -d '{"log_data":"match in rule 41", \ "set_variable":["tx.php_injection_score=+%{tx.critical_anomaly_score} ","tx.anomaly_score=+%{tx.critical_anomaly_score}","tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION-%{matched_var_name}=%{tx.0}"], \ "severity":2,"skip_after":100,"description":"Testing rule", \ "log":"true","transformations":["base64Decode","escapeSeqDecode","url Decode"], \ "audit":"false","operator":"strContains","execute":"/opt/example_scri pt.lua","action":"pass", \ "variables":["REQUEST_URI","REQUEST_BODY"],"multi_match":"true","http _code":200, \ 11 "capture":"false","skip":2,"tag":["testing","Api tests"],"rule_id":199,"operating":"index","phase":2}' http://192.168.100.150:80/api.cgi/waf/test_waf_ori/rules << Respuesta: { "description" : "Create a rule in the set test_waf_ori", "message" : "The rule was created properly.", "params" : { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [], "description" : "Testing rule", "execute" : "/opt/example_script.lua", "http_code" : "200", "id" : 0, "log" : "true", "log_data" : "match in rule 41", "multi_match" : "true", "operating" : "index", "operator" : "strContains", "phase" : "2", "raw" : "SecRule REQUEST_URI|REQUEST_BODY \"@contains index\" \"id:199,msg:'Testing rule',tag:testing,tag:Api tests,severity:2,phase:2,t:base64Decode,t:escapeSeqDecode,t:urlDecode, multimatch,pass,status:200,log,noauditlog,logdata:match in rule 41,setvar:tx.php_injection_score=+%{tx.critical_anomaly_score},setvar: tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0},skip:2,skipAfter:100,exec:/opt/example_scr ipt.lua\"", "rule_id" : "199", "set_variable" : [ "tx.php_injection_score=+%{tx.critical_anomaly_score}", "tx.anomaly_score=+%{tx.critical_anomaly_score}", "tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}" ], "severity" : "2", "skip" : "2", "skip_after" : "100", "tag" : [ "testing", "Api tests" ], "transformations" : [ "base64Decode", "escapeSeqDecode", "urlDecode" ], "type" : "match_action", "variables" : [ "REQUEST_URI", "REQUEST_BODY" ] 12 } } Crear una regla >> Petición: curl -X POST -H 'Content-Type: application/json' -d '{"raw":"SecRule TX:sampling_rnd100 "@rx ^0([0-9])" \ "id:1003,phase:1,pass""}' http://192.168.100.150:80/api.cgi/waf/test_waf_ori/rules << Respuesta: { "description" : "Create a rule in the set test_waf_ori", "message" : "The rule was created properly.", "params" : { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", "id" : 1, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "^0([0-9])", "operator" : "strRegex", "phase" : "1", "raw" : "SecRule TX:sampling_rnd100 \"@rx ^0([0-9])\" \"id:1003,phase:1,pass\"", "rule_id" : "1003", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [], "type" : "match_action", "variables" : [ "TX:sampling_rnd100" ] } } Mostrar la regla 0 >> Petición: curl -X GET http://192.168.100.150:80/api.cgi/waf/test_waf_ori/rules/0 13 << Respuesta: { "description" : "Get the WAF rule 0 of the set test_waf_ori", "params" : { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [], "description" : "Testing rule", "execute" : "/opt/example_script.lua", "http_code" : "200", "id" : 0, "log" : "true", "log_data" : "match in rule 41", "multi_match" : "true", "operating" : "index", "operator" : "strContains", "phase" : "2", "raw" : "SecRule REQUEST_URI|REQUEST_BODY \"@contains index\" \"id:199,msg:'Testing rule',tag:testing,tag:Api tests,severity:2,phase:2,t:base64Decode,t:escapeSeqDecode,t:urlDecode, multimatch,pass,status:200,log,noauditlog,logdata:match in rule 41,setvar:tx.php_injection_score=+%{tx.critical_anomaly_score},setvar: tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0},skip:2,skipAfter:100,exec:/opt/example_scr ipt.lua\"", "rule_id" : "199", "set_variable" : [ "tx.php_injection_score=+%{tx.critical_anomaly_score}", "tx.anomaly_score=+%{tx.critical_anomaly_score}", "tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}" ], "severity" : "2", "skip" : "2", "skip_after" : "100", "tag" : [ "testing", "Api tests" ], "transformations" : [ "base64Decode", "escapeSeqDecode", "urlDecode" ], "type" : "match_action", "variables" : [ "REQUEST_URI", "REQUEST_BODY" ] } } 20 "operating" : "^0([0-9])", "operator" : "strRegex", "phase" : "1", "raw" : "SecRule TX:sampling_rnd100 \"@rx ^0([0-9])\" \"id:1003,phase:1,pass\"", "rule_id" : "1003", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [], "type" : "match_action", "variables" : [ "TX:sampling_rnd100" ] }, { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", "id" : 2, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "POST", "operator" : "strEq", "phase" : "1", "raw" : "SecRule REQUEST_METHOD \"@streq POST\" \"id:9001185,phase:1,t:none,pass,nolog,noauditlog\"", "rule_id" : "9001185", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [ "none" ], "type" : "match_action", "variables" : [ "REQUEST_METHOD" ] }, { "action" : "", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", 21 "id" : 3, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "", "operator" : "", "phase" : "", "raw" : "SecRule REQUEST_COOKIES:/S?SESS[a-f0-9]+/ \"@ \" \"id:9001187,ctl:requestBodyAccess=Off\"", "rule_id" : "9001187", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [], "type" : "match_action", "variables" : [ "REQUEST_COOKIES:/S?SESS[a-f0-9]+/" ] } ] } } Mover la regla 0 a la posición 2 >> Petición: Curl -X POST -H 'Content-Type: application/json' -d '{"position":2}' http://192.168.100.150:80/api.cgi/waf/test_waf_ori/rules/0/actions << Respuesta: { "description" : "Move a rule in the set test_waf_ori", "message" : "The rule was moved properly to the position 2." } Mostrar el set “test_waf_ori” >> Petición: curl -X GET http://192.168.100.150:80/api.cgi/waf/test_waf_ori << Respuesta: { "description" : "Get the WAF set test_waf_ori", "params" : { "configuration" : { "audit" : "false", 22 "default_action" : "allow", "default_log" : "false", "default_phase" : "1", "disable_rules" : [], "process_request_body" : "false", "process_response_body" : "false", "request_body_limit" : "", "status" : "false" }, "rules" : [ { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", "id" : 0, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "^0([0-9])", "operator" : "strRegex", "phase" : "1", "raw" : "SecRule TX:sampling_rnd100 \"@rx ^0([0-9])\" \"id:1003,phase:1,pass\"", "rule_id" : "1003", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [], "type" : "match_action", "variables" : [ "TX:sampling_rnd100" ] }, { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", "id" : 1, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "POST", "operator" : "strEq", "phase" : "1", "raw" : "SecRule REQUEST_METHOD \"@streq POST\" \"id:9001185,phase:1,t:none,pass,nolog,noauditlog\"", 23 "rule_id" : "9001185", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [ "none" ], "type" : "match_action", "variables" : [ "REQUEST_METHOD" ] }, { "action" : "deny", "audit" : "false", "capture" : "true", "chain" : [], "description" : "Rule to modify the another one", "execute" : "", "http_code" : "402", "id" : 2, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "", "operator" : "detectSQLi", "phase" : "4", "raw" : "SecRule RESPONSE_HEADERS_NAMES \"@detectSQLi \" \"id:200,msg:'Rule to modify the another one',tag:testing 2,tag:Api tests 2,severity:4,phase:4,t:replaceComments,t:removeCommentsChar,capture,de ny,status:402,noauditlog,setvar:tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0},skip:1,skipAfter:12\"", "rule_id" : "200", "set_variable" : [ "tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}" ], "severity" : "4", "skip" : "1", "skip_after" : "12", "tag" : [ "testing 2", "Api tests 2" ], "transformations" : [ "replaceComments", "removeCommentsChar" ], "type" : "match_action", "variables" : [ "RESPONSE_HEADERS_NAMES" ] }, 24 { "action" : "", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", "id" : 3, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "", "operator" : "", "phase" : "", "raw" : "SecRule REQUEST_COOKIES:/S?SESS[a-f0-9]+/ \"@ \" \"id:9001187,ctl:requestBodyAccess=Off\"", "rule_id" : "9001187", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [], "type" : "match_action", "variables" : [ "REQUEST_COOKIES:/S?SESS[a-f0-9]+/" ] } ] } } Borrar la regla 0 >> Petición: curl -X DELETE http://192.168.100.150:80/api.cgi/waf/test_waf_ori/rules/0 << Respuesta: { "description" : "Delete the rule 0 from the set test_waf_ori", "message" : "The rule 0 has been deleted properly" } Mostrar el set “test_waf_ori” >> Petición: curl -X GET http://192.168.100.150:80/api.cgi/waf/test_waf_ori 25 << Respuesta: { "description" : "Get the WAF set test_waf_ori", "params" : { "configuration" : { "audit" : "false", "default_action" : "allow", "default_log" : "false", "default_phase" : "1", "disable_rules" : [], "process_request_body" : "false", "process_response_body" : "false", "request_body_limit" : "", "status" : "false" }, "rules" : [ { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", "id" : 0, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "POST", "operator" : "strEq", "phase" : "1", "raw" : "SecRule REQUEST_METHOD \"@streq POST\" \"id:9001185,phase:1,t:none,pass,nolog,noauditlog\"", "rule_id" : "9001185", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [ "none" ], "type" : "match_action", "variables" : [ "REQUEST_METHOD" ] }, { "action" : "deny", "audit" : "false", "capture" : "true", "chain" : [], "description" : "Rule to modify the another one", "execute" : "", "http_code" : "402", "id" : 1, "log" : "false", 26 "log_data" : "", "multi_match" : "false", "operating" : "", "operator" : "detectSQLi", "phase" : "4", "raw" : "SecRule RESPONSE_HEADERS_NAMES \"@detectSQLi \" \"id:200,msg:'Rule to modify the another one',tag:testing 2,tag:Api tests 2,severity:4,phase:4,t:replaceComments,t:removeCommentsChar,capture,de ny,status:402,noauditlog,setvar:tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0},skip:1,skipAfter:12\"", "rule_id" : "200", "set_variable" : [ "tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}" ], "severity" : "4", "skip" : "1", "skip_after" : "12", "tag" : [ "testing 2", "Api tests 2" ], "transformations" : [ "replaceComments", "removeCommentsChar" ], "type" : "match_action", "variables" : [ "RESPONSE_HEADERS_NAMES" ] }, { "action" : "", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", "id" : 2, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "", "operator" : "", "phase" : "", "raw" : "SecRule REQUEST_COOKIES:/S?SESS[a-f0-9]+/ \"@ \" \"id:9001187,ctl:requestBodyAccess=Off\"", "rule_id" : "9001187", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], 27 "transformations" : [], "type" : "match_action", "variables" : [ "REQUEST_COOKIES:/S?SESS[a-f0-9]+/" ] } ] } } Crear una chain en la regla 0 >> Petición: curl -X POST -H 'Content-Type: application/json' -d '{"operating":"","transformations":["replaceComments","removeCommentsC har"],"audit":"","operator":"!detectSQLi","log":"","variables":["!RESP ONSE_BODY"],"multi_match":"false","capture":"true","execute":"","set_v ariable":["tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}"]}' http://192.168.100.150:80/api.cgi/waf/test_waf_ori/rules/0/chains << Respuesta: { "description" : "Create a chain in the rule 0 for the set test_waf_ori", "message" : "Settings were changed successful.", "params" : { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [ { "capture" : "true", "execute" : "", "multi_match" : "false", "operating" : "", "operator" : "!detectSQLi", "set_variable" : [ "tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}" ], "transformations" : [ "replaceComments", "removeCommentsChar" ], "variables" : [ "!RESPONSE_BODY" ] } ], "description" : "", "execute" : "", 28 "http_code" : "", "id" : 0, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "POST", "operator" : "strEq", "phase" : "1", "raw" : "SecRule REQUEST_METHOD \"@streq POST\" \"id:9001185,phase:1,t:none,pass,nolog,noauditlog,chain\"\nSecRule !RESPONSE_BODY \"!@detectSQLi \" \"t:replaceComments,t:removeCommentsChar,capture,setvar:tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION-%{matched_var_name}=%{tx.0}\" #", "rule_id" : "9001185", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [ "none" ], "type" : "match_action", "variables" : [ "REQUEST_METHOD" ] } } Modificar la cadena 0 de la regla 0 >> Petición: curl -X PUT -H 'Content-Type: application/json' -d '{"capture":"true","multi_match":"false","variables":["&RESPONSE_BODY" ],"set_variable":["tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}"],"execute":"","operator":"!detectSQLi","t ransformations":["replaceComments","removeCommentsChar"],"operating":" ","log":""}' http://192.168.100.150:80/api.cgi/waf/test_waf_ori/rules/0/chains/0 << Respuesta: { "description" : "Modify the chain 0 in rule 0 for the set test_waf_ori", "params" : { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [ { "capture" : "true", "execute" : "", 29 "multi_match" : "false", "operating" : "", "operator" : "!detectSQLi", "set_variable" : [ "tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}" ], "transformations" : [ "replaceComments", "removeCommentsChar" ], "variables" : [ "!RESPONSE_BODY" ] } ], "description" : "", "execute" : "", "http_code" : "", "id" : 0, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "POST", "operator" : "strEq", "phase" : "1", "raw" : "SecRule REQUEST_METHOD \"@streq POST\" \"id:9001185,phase:1,t:none,pass,nolog,noauditlog,chain\"\nSecRule !RESPONSE_BODY \"!@detectSQLi \" \"t:replaceComments,t:removeCommentsChar,capture,setvar:tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION-%{matched_var_name}=%{tx.0}\" #", "rule_id" : "9001185", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [ "none" ], "type" : "match_action", "variables" : [ "REQUEST_METHOD" ] } } Borrar la cadena 0 de la regla 0 >> Petición: curl -X DELETE http://192.168.100.150:80/api.cgi/waf/test_waf_ori/rules/0/chains/0 36 tests 2,severity:4,phase:4,t:replaceComments,t:removeCommentsChar,capture,de ny,status:402,noauditlog,setvar:tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0},skip:1,skipAfter:12\"", "rule_id" : "200", "set_variable" : [ "tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}" ], "severity" : "4", "skip" : "1", "skip_after" : "12", "tag" : [ "testing 2", "Api tests 2" ], "transformations" : [ "replaceComments", "removeCommentsChar" ], "type" : "match_action", "variables" : [ "RESPONSE_HEADERS_NAMES" ] }, { "action" : "", "audit" : "false", "capture" : "false", "chain" : [], "description" : "", "execute" : "", "http_code" : "", "id" : 1, "log" : "false", "log_data" : "", "multi_match" : "false", "operating" : "", "operator" : "", "phase" : "", "raw" : "SecRule REQUEST_COOKIES:/S?SESS[a-f0-9]+/ \"@ \" \"id:9001187,ctl:requestBodyAccess=Off\"", "rule_id" : "9001187", "set_variable" : [], "severity" : "", "skip" : "", "skip_after" : "", "tag" : [], "transformations" : [], "type" : "match_action", "variables" : [ "REQUEST_COOKIES:/S?SESS[a-f0-9]+/" ] }, { 37 "id" : 2, "mark" : "mark_testing", "raw" : "SecMarker mark_testing", "type" : "marker" }, { "action" : "pass", "audit" : "false", "capture" : "false", "chain" : [], "description" : "Testing rule", "execute" : "/opt/example_script.lua", "http_code" : "200", "id" : 3, "log" : "true", "log_data" : "match in rule 41", "multi_match" : "true", "phase" : "2", "raw" : "SecAction \"id:49,msg:'Testing rule',tag:testing,tag:Api tests,severity:2,phase:2,t:base64Decode,t:escapeSeqDecode,t:urlDecode, multimatch,pass,status:200,log,noauditlog,logdata:match in rule 41,setvar:tx.php_injection_score=+%{tx.critical_anomaly_score},setvar: tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:tx.%{rule.id}- OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0},skip:2,skipAfter:100,exec:/opt/example_scr ipt.lua\"", "rule_id" : "49", "set_variable" : [ "tx.php_injection_score=+%{tx.critical_anomaly_score}" , "tx.anomaly_score=+%{tx.critical_anomaly_score}", "tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/PHP_INJECTION- %{matched_var_name}=%{tx.0}" ], "severity" : "2", "skip" : "2", "skip_after" : "100", "tag" : [ "testing", "Api tests" ], "transformations" : [ "base64Decode", "escapeSeqDecode", "urlDecode" ], "type" : "action" } ] } } Listar los sets 38 >> Petición: curl -X GET http://192.168.100.150:80/api.cgi/waf << Respuesta: { "description" : "List the WAF sets", "params" : [ "WORDPRESS-EXCLUSION", "REQUEST-903.9002-WORDPRESS-EXCLUSION-RULES", "REQUEST-911-METHOD-ENFORCEMENT", "REQUEST-901-INITIALIZATION", "tests", "RESPONSE-951-DATA-LEAKAGES-SQL", "owasp", "testWAF", "rule1", "test_waf_copied", "CUSTOM-RULESET", "test_waf_ori", "REQUEST-903.9001-DRUPAL-EXCLUSION-RULES" ] } Añadiendo el set “test_waf_ori” a una granja >> Petición: curl -X POST -H 'Content-Type: application/json' -d '{"name":"test_waf_ori"}' http://192.168.100.150:80/api.cgi/farms/httpTest/waf << Respuesta: { "description" : "Apply a WAF set to a farm", "message" : "WAF set test_waf_ori was applied properly to the farm httpTest.", "success" : "true" } Añadiendo el set “test_waf_copied” a una granja >> Petición: curl -X POST -H 'Content-Type: application/json' -d '{"name":"test_waf_copied"}' http://192.168.100.150:80/api.cgi/farms/httpTest/waf << Respuesta: 39 { "description" : "Apply a WAF set to a farm", "message" : "WAF set test_waf_copied was applied properly to the farm httpTest.", "success" : "true" } Moviendo los sets de la granja >> Petición: curl -X POST -H 'Content-Type: application/json' -d '{"position":0}' http://192.168.100.150:80/api.cgi/farms/httpTest/waf/test_waf_copied/ actions << Respuesta: { "description" : "Move a set in farm", "message" : "The set was moved properly to the position 0." } Quitando el set “test_waf_copied” a una granja >> Petición: curl -X DELETE http://192.168.100.150:80/api.cgi/farms/httpTest/waf/test_waf_copied << Respuesta: { "description" : "Unset a WAF set from a farm", "message" : "The WAF set test_waf_copied was removed successful from the farm httpTest.", "success" : "true" } Borrando el set “test_waf_copied” >> Petición: curl -X DELETE https://192.168.100.150:80/api.cgi/waf/test_waf_copied << Respuesta: { "description" : "Delete the WAF set test_waf_copied", "message" : "The WAF set test_waf_copied has been deleted successful.", 40 "success" : "true" } Borrando el set “test_waf_ori” >> Petición: Curl -X DELETE https://192.168.100.150:80/api.cgi/waf/test_waf_ori << Respuesta: { "description" : "Delete the WAF set test_waf_ori", "message" : "The WAF set test_waf_ori has been deleted successful.", "success" : "true" } 41