Discourse + Cortafuegos de Aplicación Web (WAF) mod_security

¿Cuál es el WAF recomendado para ejecutar frente a Discourse?

Recientemente logré modificar la configuración de nginx instalada en el contenedor Docker de Discourse para incluir ModSecurity y el CRS (Conjunto de Reglas Central) de OWASP (Proyecto de Seguridad de Aplicaciones Web Abiertas).

Hasta ahora, mis pruebas son excelentes y ModSecurity parece funcionar muy bien con Discourse de forma nativa.

¿Cuáles son las experiencias de otros usuarios con WAFs y Discourse? ¿Tienen alguna recomendación además de ModSecurity?

Una nota sobre la importancia de los WAF: ofrecen protección amplia contra vulnerabilidades de día cero (0days), no solo en la aplicación web, sino también en toda su pila de dependencias.

Se me ha señalado que el equipo de Discourse tiene un programa de recompensas por errores (bug bounty), ¡lo cual es excelente!

…pero, por supuesto, no existe código 100 % seguro. Los errores ocurren, y no tiene ni siquiera que ser un error de tu equipo para que se introduzca una vulnerabilidad crítica en tu proyecto.

Por ejemplo, considérese la historia reciente con CVE-2019-11043: un error en php-fpm que podía ser explotado para la ejecución remota de código en servidores que ejecutaban versiones vulnerables de php-fpm y nginx.

Sin embargo, CVE-2019-11043 puede ser completamente mitigado bloqueando las solicitudes que incluyen retornos de carro o saltos de línea:

Este es solo un ejemplo de cómo una instalación de una aplicación web puede ser explotada mediante una vulnerabilidad crítica, incluso si no hay fallos en el código de esa aplicación web en sí. En el caso de Discourse, hay muchísimo software externo incluido durante la instalación con Docker, lo que podría hacer que Discourse sea vulnerable en el futuro.

En el caso anterior, sin embargo, el ModSecurity CRS ya bloquea las solicitudes con saltos de línea y retornos de carro, protegiendo efectivamente a los servidores web que de otro modo serían vulnerables a CVE-2019-11043 antes del día cero.

Este tipo de vulnerabilidades se descubren todo el tiempo. Hay beneficios significativos en el uso de un WAF como ModSecurity para aplicaciones web.

Esta es una mala idea y no se recomienda. El beneficio de este tipo de cosas para una aplicación de JavaScript es extremadamente limitado y añade una complejidad significativa a la configuración de tu alojamiento.

relacionado: Acabo de encontrar esta guía de @joelradon sobre cómo instalar Discourse con el NAXSI WAF en nginx antes del contenedor Docker de Discourse:

No es más soportable que lo que estás preguntando arriba.

¿Si ayuda, puedo añadir también una etiqueta de no soportado allí?

Creo que el deseo de algún dispositivo mágico que mitigue automáticamente los problemas está algo equivocado en la configuración de Discourse. Tenemos un programa de recompensas y corregimos los problemas en Discourse en cuestión de horas después de que se reportan. Los sitios ejecutan tests-passed de forma predeterminada, lo que en el caso de hoy incluye commits de hoy.

Por supuesto, si estás ejecutando software que fue explotado hace años y no tienes libertad para actualizarlo por… razones… un WAF tiene sentido porque podría salvarte. Pero en el caso de Discourse, creo que es, en el mejor de los casos, equivocado.

Logré persistir con éxito mis cambios de configuración de nginx ModSecurity a través de las ejecuciones de launcher rebuild app de la siguiente manera:

Primero, actualizamos la copia local de install-nginx que proviene del repositorio discourse_docker y que se clona en /var/discourse/.

cd /var/discourse/image/base
cp install-nginx install-nginx.`date "+%Y%m%d_%H%M%S"`.orig

# agregar un bloque para extraer el módulo nginx de modsecurity justo antes de descargar el código fuente de nginx
grep 'ModSecurity' install-nginx || sed -i 's%\(curl.*nginx\.org/download.*\)%# mod_security\napt-get install -y libmodsecurity-dev modsecurity-crs\ncd /tmp\ngit clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git\n\n\1%' install-nginx

# actualizar la línea de configuración para incluir el módulo ModSecurity extraído anteriormente
sed -i '/ModSecurity/! s%^[^#]*./configure \(.*nginx.*\)%#./configure \1\n./configure \1 --add-module=/tmp/ModSecurity-nginx%' install-nginx

# agregar una línea a la sección de limpieza
grep 'rm -fr /tmp/ModSecurity-nginx' install-nginx || sed -i 's%\(rm -fr.*/tmp/nginx.*\)%rm -fr /tmp/ModSecurity-nginx\n\1%' install-nginx

Tenga en cuenta que el Dockerfile responsable de ejecutar el script install-nginx se ejecuta cuando se construye la imagen. Y la imagen solo es construida por el equipo de Discourse antes de ser subida a docker hub. Cuando se ejecuta el comando ./launcher rebuild app de Discourse, se activa (si hay actualizaciones disponibles) un docker pull, que obtiene la última imagen de Docker de Discourse desde docker hub. Nuevamente, esto no reconstruye la imagen, no ejecuta el Dockerfile ni ejecuta el script install-nginx modificado anteriormente.

La única forma (que yo sepa) de activar la ejecución del script bash actualizado install-nginx (que es ejecutado por el Dockerfile) es que Docker construya una nueva imagen. Por ejemplo, esto activa a Docker para construir una nueva imagen llamada discourse_modsecurity, la cual se construirá utilizando el script install-nginx modificado localmente:

docker build --tag 'discourse_modsecurity' /var/discourse/image/base/

Desafortunadamente, no pude encontrar una manera de decirle a launcher que use una imagen personalizada (especificar un run-image utiliza la imagen especificada directamente, sin ejecutar las plantillas sobre ella, como se necesita para configurar [en lugar de solo instalar] nginx). Por lo tanto, reemplazamos la variable image definida en el script launcher para usar nuestra nueva imagen de Docker local llamada discourse_modsecurity.

# reemplazar la línea "image="discourse/base:<version>" con 'image="discourse_modsecurity"'
grep 'discourse_modsecurity' launcher || sed --in-place=.`date "+%Y%m%d_%H%M%S"` '/base_image/! s%^\(\s*\)image=\(.*\)$%#\1image=\2\n\1image="discourse_modsecurity"%' /var/discourse/launcher

Ahora agregamos un nuevo archivo de plantilla para configurar nuestras configuraciones de nginx e incluir los archivos/bloques necesarios de modsecurity.

cat << EOF > /var/discourse/templates/web.modsecurity.template.yml
run:
  - exec:
     cmd:
       - sudo apt-get install -y modsecurity-crs
       - cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
       - sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
       - sed -i 's^\(\s*\)[^#]*SecRequestBodyInMemoryLimit\(.*\)^\1#SecRequestBodyInMemoryLimit\2^' /etc/modsecurity/modsecurity.conf
       - sed -i '/nginx/! s%^\(\s*\)[^#]*SecAuditLog \(.*\)%#\1SecAuditLog \2\n\1SecAuditLog /var/log/nginx/modsec_audit.log%' /etc/modsecurity/modsecurity.conf

  - file:
     path: /etc/nginx/conf.d/modsecurity.include
     contents: |
        ################################################################################
        # Archivo:    modsecurity.include
        # Versión: 0.1
        # Propósito: Define reglas de mod_security para el vhost de discourse
        #          Esto debe incluirse en los bloques server{} de los vhosts de nginx.
        # Autor:  Michael Altfield <michael@opensourceecology.org>
        # Creado: 2019-11-12
        # Actualizado: 2019-11-12
        ################################################################################
        Include "/etc/modsecurity/modsecurity.conf"
        
        # OWASP Core Rule Set, instalado desde el paquete 'modsecurity-crs' en debian
        Include /etc/modsecurity/crs/crs-setup.conf
        Include /usr/share/modsecurity-crs/rules/*.conf

  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /server.+{/
     to: |
       server {
         modsecurity on;
         modsecurity_rules_file /etc/nginx/conf.d/modsecurity.include;

EOF

Y agregamos esta plantilla (templates/web.modsecurity.template.yml) al bloque de plantillas del archivo de configuración yaml de nuestra aplicación, para que se vea algo así:

[root@osestaging1 discourse]# vim /var/discourse/containers/app.yml
...
[root@osestaging1 discourse]# grep -A 6 'templates:' /var/discourse/containers/app.yml
templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
  - "templates/web.modsecurity.template.yml"
[root@osestaging1 discourse]# 

Ahora, cuando reconstruyas la aplicación de Docker de Discourse, usará tu nueva imagen de Docker discourse_modsecurity con nginx y modsecurity, y configurará nginx para utilizar el OWASP CRS.

/var/discourse/launcher rebuild app

Estoy de acuerdo contigo en que ModSecurity o firewalls de aplicaciones web (WAF) similares no son una solución infalible.

Pero conozco (al menos) una vulnerabilidad encontrada en RoR que afectó a Discourse que habría sido mitigada por un mecanismo similar a ModSecurity.

Mientras aplicábamos el parche, vimos en nuestros registros que esta vulnerabilidad ya se había explotado en la naturaleza en al menos uno de nuestros foros antes de que la CVE fuera conocida públicamente y parcheada. Esto no resultó en ninguna divulgación de información, pero eso fue solo porque hicimos algunas cosas de manera diferente a una instalación estándar (es decir, suerte).

Sin embargo, no estoy seguro de si la complejidad añadida compensa la seguridad adicional.

Comparo los WAF con los antivirus basados en firmas, los cuales, en mi opinión, no son muy útiles en entornos con una superficie de ataque limitada (por ejemplo, sus servidores que no son Windows).

No van a detectar todo, pero (en teoría) sí capturarán patrones comunes de ataques (por ejemplo, inyección SQL) y exploits conocidos (por ejemplo, la vulnerabilidad de RoR mencionada anteriormente) en una variedad de software.

Puedo ver el valor de ejecutarlos, especialmente en un entorno empresarial donde tienes una gran cantidad de aplicaciones ejecutando cosas desconocidas y necesitas asegurarte de que cada aplicación está protegida contra estos exploits sin tener que preocuparte por cada una individualmente; esto convierte un problema de tamaño NxM en uno de tamaño N+M.

Si vale la pena ejecutarlos en tu sitio es otra cuestión y uno de esos análisis de riesgo (de rotura) y beneficio (de protección) que tendrás que realizar por tu cuenta.