Reactive Web Firewall: dal 403 al ban nftables in tempo reale

Implementazione pratica di un Reactive Web Firewall: modulo Apache, regole applicative, helper Unix socket, fast-ban nftables, whitelist, test end-to-end e troubleshooting.

Quando si parla di protezione di un server web, molto spesso si ragiona per compartimenti stagni. Il firewall si occupa di indirizzi IP e porte; Apache o nginx si occupano di HTTP; l’applicazione pensa ai propri utenti e ai propri permessi. Il problema è che un attacco reale attraversa tutti questi livelli contemporaneamente.

Prendiamo una richiesta verso /.git/config. Per il firewall di rete è semplicemente una connessione TCP valida verso la porta 443, identica a migliaia di altre. Per Apache, invece, quella richiesta ha un significato preciso: qualcuno sta cercando un file che normalmente non dovrebbe mai essere pubblico. Lo stesso vale per tentativi verso .env, vecchie console amministrative, path di exploit conosciuti o sequenze di richieste che hanno senso soltanto durante una scansione.

Da questa differenza nasce l’idea di un Reactive Web Firewall: lasciare che il livello HTTP riconosca il comportamento e usare quel segnale per reagire anche a livello di rete. Il primo vantaggio è immediato: dopo il primo match non siamo costretti a far arrivare allo stack web altre cento richieste dello stesso client. Il secondo è architetturale: detection ed enforcement rimangono separati, quindi Apache non deve trasformarsi in un gestore di nftables.

WAF e firewall non sono la stessa cosa

Un WAF decide normalmente sul singolo evento applicativo: questa richiesta passa, questa viene rifiutata. Un firewall stateful decide invece se un flusso di rete può esistere. RwF mette in comunicazione i due mondi senza confonderli: la regola applicativa genera un evento ad alta confidenza, un helper controllato lo traduce in un ban temporaneo e nftables applica la decisione ai pacchetti successivi.

Questa distinzione è importante anche per la sicurezza del sistema stesso. Dare al processo Apache privilegi generali sul firewall significherebbe ampliare enormemente il danno possibile in caso di compromissione del web server. Un helper con un protocollo minimale e un compito limitato riduce invece la superficie.

Il vero problema: sapere chi stiamo bannando

La parte più delicata non è aggiungere un elemento a un set nftables. È stabilire quale indirizzo debba entrarci. Se davanti ad Apache c’è un reverse proxy o una CDN, l’IP della connessione TCP appartiene al proxy. Bannarlo significherebbe poter oscurare il sito a migliaia di utenti con una sola richiesta ostile.

Per questo la catena Real-IP è una parte integrante dell’architettura, non un dettaglio di logging. Un header come X-Forwarded-For ha valore soltanto se sappiamo chi ce lo ha consegnato. Se qualunque client può raggiungere direttamente il backend e scegliere liberamente quell’header, può anche scegliere chi farci bannare.

Ban temporaneo, non lista nera eterna

Infine c’è una scelta di filosofia. Un errore di detection è sempre possibile, quindi il ban deve essere proporzionato. I set nftables con timeout sono particolarmente adatti: l’indirizzo entra, resta bloccato per il tempo previsto e poi scompare automaticamente. Non serve una cronologia infinita di IP e un falso positivo non diventa una condanna permanente.

Con questo modello in mente possiamo passare alla realizzazione concreta.

Architettura

L’implementazione usata qui è composta da quattro pezzi separati: mod_rwf dentro Apache, un helper asincrono raggiunto tramite Unix socket, un meccanismo di fast-ban locale basato su nftables e, opzionalmente, la propagazione del ban verso il firewall di frontiera.

Apache + mod_rwf
        |
        | match regola
        v
403 + evento al helper
        |
        v
/run/reactive-web-firewall/helper.sock
        |
        v
fastban_v4 / fastban_v6 (nftables)
        |
        +--> eventuale propagazione al firewall di frontiera

Prerequisiti e compilazione del modulo Apache

Il modulo va compilato contro la stessa ABI di Apache in uso. Su Debian/Ubuntu il pacchetto utile è apache2-dev, che fornisce apxs.

apt update
apt install apache2-dev build-essential

apxs -c mod_rwf.c
apxs -i mod_rwf.la

apache2ctl configtest
systemctl reload apache2

Dopo l’installazione controlliamo che il modulo sia realmente caricato:

apache2ctl -M | grep rwf

Configurazione Apache

Le regole vengono mantenute fuori dai VirtualHost, in un file dedicato, per esempio /etc/reactive-web-firewall/apache-rules.conf. Il socket dell’helper è /run/reactive-web-firewall/helper.sock.

RwfEnabled On

RwfWhitelistIP 127.0.0.1
RwfWhitelistIP 10.0.20.0/24

RwfRuleExact  "/__rwf-test__"               "test"
RwfRulePrefix "/.git/"                      "git-probe"
RwfRulePrefix "/wp-config.php"              "wp-config-probe"
RwfRuleRegex  "(?i)/(\.env|\.svn|id_rsa)" "secret-probe"

Include /etc/reactive-web-firewall/apache-rules.conf

Dopo ogni modifica:

apache2ctl configtest && systemctl reload apache2

Fast-ban con nftables

Nel nostro schema i set si chiamano fastban_v4 e fastban_v6 dentro la tabella inet custom_web_fastban. I timeout fanno sì che il blocco si auto-estingua senza dover mantenere una lista infinita di IP.

nft list table inet custom_web_fastban
nft list set inet custom_web_fastban fastban_v4
nft list set inet custom_web_fastban fastban_v6

La parte legacy del fast-ban è gestita da /usr/local/sbin/custom-web-fastban e dalla configurazione nftables in /etc/nftables.d/custom-web-fastban.nft. Il servizio che carica le regole deve risultare attivo:

systemctl status custom-web-fastban.service
nft list ruleset | sed -n '/custom_web_fastban/,+80p'

Test end-to-end

Creiamo volutamente una regola innocua sul path di test e richiamiamola da una macchina non in whitelist.

curl -i https://www.example.net/__rwf-test__

La risposta attesa è 403. Subito dopo controlliamo il ban:

nft list set inet custom_web_fastban fastban_v4

# Nel nostro ambiente è disponibile anche:
check-fw-ban 203.0.113.25

Il controllo deve dirci non soltanto “presente/non presente”, ma anche dove: allowlist banIP, set temporaneo locale, firewall di frontiera e timeout residuo.

Reverse proxy e CDN: non bannare Cloudflare

Se Apache riceve la connessione da un reverse proxy o da una CDN, l’IP TCP visto dal backend non è necessariamente quello dell’attaccante. Il sistema deve fidarsi dell’header che contiene il client reale solo quando la connessione arriva da proxy esplicitamente autorizzati. In caso contrario un client potrebbe forgiare l’header e far bannare un indirizzo arbitrario.

Debug

# Apache
LogLevel alert rwf:debug

journalctl -u apache2 -f
journalctl -u custom-web-fastban.service -f

ss -xl | grep reactive-web-firewall
ls -l /run/reactive-web-firewall/helper.sock

Per misurare il costo del modulo possono essere registrati anche i timestamp apache_start_us, rwf_enter_us, rwf_exit_us e apache_end_us. È utile per dimostrare che la decisione applicativa non sta aggiungendo latenze rilevanti.

Cosa deve restare fuori da RwF

RwF non sostituisce patching, autenticazione, rate limiting, segmentazione e logging. È una reazione rapida a un segnale ad alta confidenza. Le regole devono essere poche, spiegabili e verificabili.