Discourse + Web Application Firewall (WAF) mod_security

Qual è il WAF consigliato da eseguire davanti a Discourse?

Recentemente sono riuscito a modificare la configurazione di nginx installata nel container Docker di Discourse per includere ModSecurity e il CRS (Core Rule Set) di OWASP (Open Web Application Security Project).

Finora i miei test sono stati eccellenti e ModSecurity sembra funzionare molto bene con Discourse out-of-the-box.

Quali sono le esperienze di altri utenti con WAF e Discourse? Avete altre raccomandazioni oltre a ModSecurity?

Una nota sul perché i WAF sono importanti: offrono una protezione ampia contro le vulnerabilità 0-day non solo a livello dell’applicazione web, ma anche dell’intera pila di dipendenze su cui essa si basa.

Mi è stato fatto notare che il team di Discourse ha un programma di bug bounty, il che è ottimo!

…ma, naturalmente, non esiste codice sicuro al 100%. Gli errori accadono, e non è nemmeno necessario che si tratti di un errore del proprio team per introdurre una vulnerabilità critica nel proprio progetto.

Ad esempio, si consideri la storia recente con CVE-2019-11043: un bug in php-fpm che poteva essere sfruttato per l’esecuzione remota di codice su server con versioni vulnerabili di php-fpm e nginx.

Tuttavia, CVE-2019-11043 può essere completamente mitigato bloccando le richieste che includono caratteri di ritorno a capo o a capo:

Questo è solo un esempio di come un’installazione di un’applicazione web possa essere sfruttata tramite una vulnerabilità critica, anche se non ci sono difetti nel codice dell’applicazione web stessa. Nel caso di Discourse, ci sono molte componenti software esterne integrate durante l’installazione Docker che potrebbero rendere Discourse vulnerabile in futuro.

Nel caso sopra citato, tuttavia, il ModSecurity CRS già blocca le richieste contenenti a capo e ritorno a capo, proteggendo efficacemente server web altrimenti vulnerabili a CVE-2019-11043 prima ancora del giorno zero.

Tali vulnerabilità vengono scoperte costantemente. Esistono significativi vantaggi nell’utilizzare un WAF come ModSecurity per le applicazioni web.

Questa è una cattiva idea e non è consigliata. Il vantaggio di questo tipo di approccio per un’applicazione JavaScript è estremamente limitato e aggiunge una complessità significativa alla configurazione del tuo hosting.

Correlato: Ho appena trovato questa guida di @joelradon su come installare Discourse con NAXSI WAF in nginx prima del contenitore Docker di Discourse:

Non è più supportabile di quanto tu stia chiedendo sopra.

Se può aiutare, posso aggiungere anche lì un tag ‘non supportato’?

Penso che il desiderio di un dispositivo magico che mitighi automaticamente i problemi sia in qualche modo fuorviante nell’ambito della configurazione di Discourse. Abbiamo un programma di bounty e risolviamo le problematiche in Discourse entro poche ore dalla loro segnalazione. I siti eseguono tests-passed per impostazione predefinita, che nel caso di oggi include commit di oggi.

Certo, se stai utilizzando un software sfruttato anni fa e non hai la libertà di aggiornarlo a causa di… varie ragioni… un WAF ha senso perché potrebbe salvaguardarti. Ma nel caso di Discourse, penso che sia al massimo fuorviante.

Sono riuscito con successo a mantenere le modifiche alla configurazione di nginx ModSecurity tra le esecuzioni di launcher rebuild app come segue:

Per prima cosa, aggiorniamo la copia locale di install-nginx che proviene dal repository discourse_docker ed è clonata in /var/discourse/.

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

# aggiungi un blocco per controllare il modulo nginx di modsecurity subito prima di scaricare il sorgente di 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

# aggiorna la riga di configurazione per includere il modulo ModSecurity controllato sopra
sed -i '/ModSecurity/! s%^[^#]*./configure \(.*nginx.*\)%#./configure \1\n./configure \1 --add-module=/tmp/ModSecurity-nginx%' install-nginx

# aggiungi una riga alla sezione di pulizia
grep 'rm -fr /tmp/ModSecurity-nginx' install-nginx || sed -i 's%\(rm -fr.*/tmp/nginx.*\)%rm -fr /tmp/ModSecurity-nginx\n\1%' install-nginx

Nota che il Dockerfile responsabile dell’esecuzione dello script install-nginx viene eseguito quando l’immagine viene costruita. L’immagine viene costruita solo dal team di Discourse prima di essere caricata su docker hub. Quando viene eseguito il comando Discourse ./launcher rebuild app, viene attivato (se sono disponibili aggiornamenti) un docker pull, che scarica l’ultima immagine docker di Discourse da docker hub. Ancora una volta, questo non ricostruisce l’immagine, non esegue il Dockerfile né esegue lo script install-nginx modificato sopra.

L’unico modo (che io sappia) per attivare l’esecuzione dello script bash aggiornato install-nginx (che viene eseguito dal Dockerfile) è far sì che docker costruisca una nuova immagine. Ad esempio, questo attiva docker per costruire una nuova immagine denominata discourse_modsecurity – che verrà costruita utilizzando lo script install-nginx modificato localmente:

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

Sfortunatamente, non sono riuscito a trovare un modo per dire a launcher di utilizzare un’immagine personalizzata (specificare un run-image utilizza l’immagine specificata direttamente, senza eseguire i template su di essa – come necessario per configurare effettivamente [anziché solo installare] nginx). Quindi sostituiamo la variabile image definita nello script launcher per utilizzare la nostra nuova immagine docker locale denominata discourse_modsecurity

# sostituisci la riga "image="discourse/base:<versione>" 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

Ora aggiungiamo un nuovo file di template per impostare le nostre configurazioni nginx per includere i file/blocchi modsecurity necessari

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: |
        ################################################################################
        # File:    modsecurity.include
        # Version: 0.1
        # Purpose: Definisce le regole mod_security per il vhost discourse
        #          Questo dovrebbe essere incluso nei blocchi server{} dei vhost nginx.
        # Author:  Michael Altfield <michael@opensourceecology.org>
        # Created: 2019-11-12
        # Updated: 2019-11-12
        ################################################################################
        Include "/etc/modsecurity/modsecurity.conf"
        
        # OWASP Core Rule Set, installato dal pacchetto 'modsecurity-crs' in 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

E aggiungiamo questo template (templates/web.modsecurity.template.yml) al blocco templates del file di configurazione yaml della nostra app, in modo che assomigli a qualcosa del genere:

[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]# 

Ora, quando ricostruisci l’app docker di Discourse, utilizzerà la tua nuova immagine docker discourse_modsecurity con nginx e modsecurity e configurerà nginx per utilizzare l’OWASP CRS.

/var/discourse/launcher rebuild app

Concordo con te che ModSecurity o WAF simili non siano una soluzione miracolosa.

Ma conosco (almeno?) una vulnerabilità individuata in RoR che ha interessato Discourse che sarebbe stata mitigata da un meccanismo simile a ModSecurity.

Mentre applicavamo la patch, abbiamo visto nei nostri log che era stata utilizzata in produzione su almeno uno dei nostri forum prima che la CVE fosse resa pubblica e corretta. Questo non ha portato a nessuna divulgazione di informazioni, ma ciò è accaduto solo perché abbiamo fatto alcune cose in modo diverso rispetto a un’installazione standard (cioè fortuna).

Non sono sicuro che la complessità aggiuntiva superi il beneficio in termini di sicurezza.

Paragono i WAF agli scanner di virus basati su firme, che a mio avviso non sono molto utili in ambienti con una superficie di attacco limitata (ad esempio, i tuoi server non Windows).

Non rileveranno tutto, ma (in teoria) cattureranno pattern comuni di attacchi (ad esempio, SQLI) e exploit noti (ad esempio, la vulnerabilità RoR citata sopra) su una gamma di software.

Riconosco il valore del loro utilizzo, specialmente in un ambiente aziendale dove hai un’enorme quantità di applicazioni che eseguono chissà cosa e hai bisogno di sapere che ogni applicazione è protetta da questi exploit senza dover preoccuparti di ciascuna singolarmente: questo trasforma un problema di dimensioni NxM in uno di dimensioni N+M.

Se ne valga la pena sul tuo sito è un’altra questione e richiede un’analisi rischio (di malfunzionamento)-beneficio (di protezione) che dovrai condurre autonomamente.