{"id":274,"date":"2026-06-20T17:01:10","date_gmt":"2026-06-20T17:01:10","guid":{"rendered":"https:\/\/shattered.io\/it\/2026\/06\/20\/web-application-firewall-modsecurity-nodejs\/"},"modified":"2026-06-20T17:02:35","modified_gmt":"2026-06-20T17:02:35","slug":"web-application-firewall-modsecurity-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/it\/2026\/06\/20\/web-application-firewall-modsecurity-nodejs\/","title":{"rendered":"WAF con ModSecurity e Node.js: 12 Step, 30 Min [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Un <strong>Web Application Firewall (WAF)<\/strong> con ModSecurity e NGINX \u00e8 il modo pi\u00f9 robusto per proteggere un&#8217;applicazione Node.js dagli attacchi pi\u00f9 comuni: SQL injection, cross-site scripting, path traversal e centinaia di exploit noti. In questa guida passo dopo passo imparerai a installare ModSecurity 3.x su Ubuntu 24.04, integrarlo con NGINX come reverse proxy e applicare le regole OWASP Core Rule Set (CRS) alla tua app Express. Tempo stimato: 30 minuti su un server pulito.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il benchmark 2026 sui WAF enterprise ha misurato un True Positive Rate del 99,56% per la configurazione ModSecurity con profilo critico, con un False Positive Rate dello 0,563%. Cloud provider come AWS WAF e Azure WAF raggiungono il 97,5%, ma a costi mensili significativi. ModSecurity, open source e gratuito, consente lo stesso livello di protezione su infrastruttura propria.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisiti-e-versioni\">Prerequisiti e versioni<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di iniziare, assicurati di avere:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Ubuntu 24.04 LTS<\/strong> (Noble Numbat) con accesso root o sudo<\/li>\n<li><strong>Node.js 22.x LTS<\/strong> con un&#8217;applicazione Express in ascolto sulla porta 3000<\/li>\n<li><strong>NGINX 1.24.x<\/strong> o versione compatibile (per il connettore dinamico)<\/li>\n<li><strong>RAM minima 2 GB<\/strong> (la compilazione di ModSecurity richiede risorse)<\/li>\n<li><strong>Disco libero 4 GB<\/strong> per i sorgenti, i moduli e i log<\/li>\n<li><strong>Git 2.x<\/strong> installato<\/li>\n<li>Un certificato SSL\/TLS attivo (usa <a href=\"\/it\/certbot-lets-encrypt-https\/\">Let&#8217;s Encrypt e Certbot<\/a> per ottenerlo gratuitamente)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Il tutorial copre l&#8217;installazione da sorgente. Sebbene esistano pacchetti precompilati, la versione da sorgente garantisce la compatibilit\u00e0 ABI con la versione NGINX in uso e consente di personalizzare le opzioni di compilazione.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"architettura-waf-davanti-a-node-js\">Architettura: WAF davanti a Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il flusso delle richieste HTTP\/HTTPS nella configurazione che costruiamo \u00e8 il seguente:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Client<\/strong> \u2192 <strong>NGINX (porta 443, SSL)<\/strong> \u2192 <strong>ModSecurity + OWASP CRS (ispezione Layer 7)<\/strong> \u2192 <strong>Node.js\/Express (porta 3000)<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">NGINX funge da reverse proxy TLS-terminante. Prima di inoltare ogni richiesta all&#8217;app Node.js, il modulo dinamico ModSecurity intercetta il traffico, applica le regole CRS e blocca tutto ci\u00f2 che corrisponde a un pattern malevolo. Se una richiesta viene bloccata, NGINX restituisce un HTTP 403 al client senza che Node.js riceva mai i dati. Questo approccio offre tre vantaggi: la logica applicativa rimane separata dalla logica di sicurezza, il WAF pu\u00f2 essere aggiornato senza modificare il codice dell&#8217;app, e lo stack di protezione \u00e8 riutilizzabile su qualsiasi backend (PHP, Python, Go).<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Componente<\/th><th>Ruolo<\/th><th>Versione 2026<\/th><\/tr><\/thead><tbody><tr><td>ModSecurity<\/td><td>Engine WAF open source<\/td><td>v3\/master (branch stabile)<\/td><\/tr><tr><td>ModSecurity-nginx<\/td><td>Connettore modulo dinamico<\/td><td>Compatibile con NGINX 1.24.x<\/td><\/tr><tr><td>OWASP CRS<\/td><td>Set di regole per OWASP Top 10<\/td><td>Ultima versione dal repo GitHub<\/td><\/tr><tr><td>NGINX<\/td><td>Reverse proxy + TLS termination<\/td><td>1.24.0<\/td><\/tr><tr><td>Node.js<\/td><td>Backend applicativo<\/td><td>22.x LTS<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-1-installare-le-dipendenze-di-compilazione\">Step 1: Installare le dipendenze di compilazione<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ModSecurity 3.x non \u00e8 disponibile come pacchetto .deb diretto per Ubuntu 24.04 con il connettore NGINX. \u00c8 necessario compilare dalla sorgente. Aggiorna i repository e installa tutte le dipendenze in un&#8217;unica operazione:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update && sudo apt upgrade -y\n\nsudo apt install -y \\\n  git build-essential autoconf automake libtool pkg-config \\\n  libpcre2-dev libxml2-dev libyajl-dev liblmdb-dev \\\n  libcurl4-openssl-dev libssl-dev doxygen \\\n  libgeoip-dev libmaxminddb-dev liblua5.3-dev \\\n  libnghttp2-dev libgnutls28-dev zlib1g-dev \\\n  wget curl<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il pacchetto <code>libpcre2-dev<\/code> \u00e8 richiesto per il pattern matching delle regole CRS. <code>libyajl-dev<\/code> abilita il parsing JSON per l&#8217;ispezione dei corpi delle richieste REST\/API. <code>libmaxminddb-dev<\/code> aggiunge il supporto GeoIP2 per bloccare o limitare il traffico per paese.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verifica che tutti i pacchetti siano stati installati correttamente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>dpkg -l | grep -E 'libpcre2|libxml2|libyajl' | awk '{print $2, $3}'<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-2-compilare-modsecurity-3-x\">Step 2: Compilare ModSecurity 3.x<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Posizionati nella directory dei sorgenti e clona il repository ufficiale di ModSecurity dal branch stabile v3:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/usr\/local\/src\n\nsudo git clone --depth 1 -b v3\/master --single-branch \\\n  https:\/\/github.com\/SpiderLabs\/ModSecurity\n\ncd ModSecurity\n\nsudo git submodule init\nsudo git submodule update\n\nsudo .\/build.sh\nsudo .\/configure\nsudo make -j\"$(nproc)\"\nsudo make install<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La compilazione richiede da 5 a 15 minuti a seconda della CPU disponibile. L&#8217;opzione <code>-j\"$(nproc)\"<\/code> usa tutti i core disponibili per parallelizzare la build. Al termine, la libreria viene installata in <code>\/usr\/local\/modsecurity\/<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Verifica l&#8217;installazione controllando il percorso della libreria condivisa:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ls -la \/usr\/local\/modsecurity\/lib\/\n# Output atteso:\n# libmodsecurity.a\n# libmodsecurity.so -> libmodsecurity.so.3\n# libmodsecurity.so.3 -> libmodsecurity.so.3.0.x<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema comune n. 1:<\/strong> Se <code>make<\/code> fallisce con <code>error: 'yajl_config' undeclared<\/code>, la versione di <code>libyajl-dev<\/code> non \u00e8 compatibile. Installa la versione specifica:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt install libyajl2 libyajl-dev\nsudo ldconfig<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-3-costruire-il-connettore-modsecurity-per-nginx\">Step 3: Costruire il connettore ModSecurity per NGINX<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il connettore \u00e8 un modulo dinamico per NGINX che fa da bridge tra il processo NGINX e la libreria ModSecurity. Deve essere compilato contro la stessa versione di NGINX che eseguirai in produzione.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/usr\/local\/src\n\nsudo git clone https:\/\/github.com\/SpiderLabs\/ModSecurity-nginx.git\n\n# Scarica i sorgenti della stessa versione di NGINX che hai installato\nNGINX_VER=$(nginx -v 2>&1 | grep -oP '[\\d.]+')\nsudo wget \"http:\/\/nginx.org\/download\/nginx-${NGINX_VER}.tar.gz\"\nsudo tar -xzf \"nginx-${NGINX_VER}.tar.gz\"\n\ncd \"nginx-${NGINX_VER}\"\n\nsudo .\/configure \\\n  --with-compat \\\n  --add-dynamic-module=..\/ModSecurity-nginx\n\nsudo make modules\n\nsudo cp objs\/ngx_http_modsecurity_module.so \/usr\/lib\/nginx\/modules\/\nsudo chmod 0644 \/usr\/lib\/nginx\/modules\/ngx_http_modsecurity_module.so<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;opzione <code>--with-compat<\/code> abilita la modalit\u00e0 di compatibilit\u00e0 ABI, indispensabile per caricare il modulo dinamico nell&#8217;NGINX installato via apt. Senza questa opzione, NGINX rifiuter\u00e0 il modulo al caricamento con un errore di versione.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema comune n. 2:<\/strong> Se ottieni <code>nginx: [emerg] module ... version 1024000 instead of 1024002<\/code>, la versione dei sorgenti NGINX scaricata non corrisponde all&#8217;NGINX installato. Usa esattamente la stessa versione:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nginx -v\n# nginx version: nginx\/1.24.0\n# Scarica NGINX 1.24.0, non altra versione<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-4-configurare-nginx-con-modsecurity\">Step 4: Configurare NGINX con ModSecurity<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Crea la directory di configurazione di ModSecurity e copia i file base:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo mkdir -p \/etc\/nginx\/modsec\n\n# Copia i file di configurazione base da ModSecurity\nsudo cp \/usr\/local\/src\/ModSecurity\/modsecurity.conf-recommended \\\n  \/etc\/nginx\/modsec\/modsecurity.conf\n\nsudo cp \/usr\/local\/src\/ModSecurity\/unicode.mapping \\\n  \/etc\/nginx\/modsec\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Carica il modulo dinamico all&#8217;inizio di <code>\/etc\/nginx\/nginx.conf<\/code> aggiungendo questa riga nella sezione principale (prima di qualsiasi blocco <code>events<\/code> o <code>http<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>load_module modules\/ngx_http_modsecurity_module.so;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Configura il server block per il tuo dominio. Sostituisci <code>example.com<\/code> con il tuo dominio e assicurati che i percorsi dei certificati SSL corrispondano a quelli generati da Certbot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>server {\n    listen 443 ssl http2;\n    server_name example.com;\n\n    ssl_certificate \/etc\/letsencrypt\/live\/example.com\/fullchain.pem;\n    ssl_certificate_key \/etc\/letsencrypt\/live\/example.com\/privkey.pem;\n    ssl_protocols TLSv1.2 TLSv1.3;\n    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;\n\n    modsecurity on;\n    modsecurity_rules_file \/etc\/nginx\/modsec\/modsecurity.conf;\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:3000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_read_timeout 90s;\n    }\n}\n\nserver {\n    listen 80;\n    server_name example.com;\n    return 301 https:\/\/$host$request_uri;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La direttiva <code>proxy_set_header X-Forwarded-For<\/code> \u00e8 fondamentale: passa l&#8217;IP reale del client all&#8217;app Node.js, che altrimenti vedrebbe solo l&#8217;indirizzo localhost di NGINX. In Express, abilita la fiducia nel proxy con <code>app.set('trust proxy', 1)<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-5-installare-owasp-core-rule-set\">Step 5: Installare OWASP Core Rule Set<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;OWASP Core Rule Set (CRS) \u00e8 il set di regole open source pi\u00f9 usato per ModSecurity. Copre le vulnerabilit\u00e0 OWASP Top 10, incluse SQL injection, XSS, command injection, path traversal e remote file inclusion. Senza CRS, ModSecurity \u00e8 solo un engine senza regole.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo git clone https:\/\/github.com\/coreruleset\/coreruleset.git \\\n  \/etc\/nginx\/owasp-crs\n\n# Copia il file di configurazione esempio\nsudo cp \/etc\/nginx\/owasp-crs\/crs-setup.conf.example \\\n  \/etc\/nginx\/owasp-crs\/crs-setup.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il CRS funziona su un sistema di <strong>punteggio anomalia<\/strong>: ogni regola che scatta aggiunge un punteggio alla richiesta. Quando il punteggio supera la soglia (default 5 per richieste in ingresso), la richiesta viene bloccata. Questo approccio riduce i falsi positivi rispetto al blocco rule-per-rule.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il <strong>Paranoia Level<\/strong> controlla quante regole vengono attivate. Il livello 1 (default) copre le minacce pi\u00f9 comuni con pochissimi falsi positivi. Il livello 4 attiva tutti i controlli con rischio elevato di bloccare traffico legittimo:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Paranoia Level<\/th><th>Regole attive<\/th><th>Falsi positivi<\/th><th>Caso d&#8217;uso<\/th><\/tr><\/thead><tbody><tr><td>1 (default)<\/td><td>Solo le pi\u00f9 critiche<\/td><td>Molto bassi<\/td><td>Produzione, primo deploy<\/td><\/tr><tr><td>2<\/td><td>Moderatamente esteso<\/td><td>Bassi<\/td><td>App con input utente complesso<\/td><\/tr><tr><td>3<\/td><td>Ampiamente esteso<\/td><td>Medi<\/td><td>Ambienti ad alta sicurezza<\/td><\/tr><tr><td>4<\/td><td>Tutte le regole<\/td><td>Alti<\/td><td>Solo con tuning approfondito<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Modifica il file <code>crs-setup.conf<\/code> per impostare il Paranoia Level:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># In \/etc\/nginx\/owasp-crs\/crs-setup.conf\nSecAction \\\n  \"id:900000, \\\n   phase:1, \\\n   nolog, \\\n   pass, \\\n   t:none, \\\n   setvar:tx.paranoia_level=1\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ora aggiorna <code>\/etc\/nginx\/modsec\/modsecurity.conf<\/code> per includere il CRS. Modifica la riga <code>SecRuleEngine<\/code> e aggiungi gli include alla fine del file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># In \/etc\/nginx\/modsec\/modsecurity.conf\n\n# Cambia da DetectionOnly a On quando sei pronto per il blocco\nSecRuleEngine On\n\nSecRequestBodyAccess On\nSecResponseBodyAccess Off\nSecRequestBodyLimit 13107200\nSecRequestBodyNoFilesLimit 131072\nSecRequestBodyLimitAction Reject\n\nSecAuditEngine RelevantOnly\nSecAuditLog \/var\/log\/modsec_audit.log\nSecAuditLogFormat JSON\nSecAuditLogParts ABIJDEFHZ\n\n# Include OWASP CRS\nInclude \/etc\/nginx\/owasp-crs\/crs-setup.conf\nInclude \/etc\/nginx\/owasp-crs\/rules\/*.conf<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-6-preparare-lapplicazione-node-js\">Step 6: Preparare l&#8217;applicazione Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di testare il WAF, assicurati che l&#8217;app Node.js sia configurata per funzionare correttamente dietro un reverse proxy. Ecco un&#8217;app Express minimale che funzioner\u00e0 con la nostra configurazione:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ app.js - Express app protetta da WAF\nconst express = require('express');\nconst app = express();\n\n\/\/ Fiducia nel reverse proxy NGINX\napp.set('trust proxy', 1);\n\napp.use(express.json({ limit: '10mb' }));\napp.use(express.urlencoded({ extended: true }));\n\n\/\/ Endpoint di test: riceve parametri GET\napp.get('\/search', (req, res) => {\n  const query = req.query.q || '';\n  res.json({\n    query: query,\n    clientIP: req.ip,\n    forwardedFor: req.headers['x-forwarded-for']\n  });\n});\n\n\/\/ Endpoint di test: riceve body JSON\napp.post('\/data', (req, res) => {\n  res.json({ received: req.body });\n});\n\napp.listen(3000, '127.0.0.1', () => {\n  console.log('App in ascolto su 127.0.0.1:3000');\n});\n\nmodule.exports = app;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nota che l&#8217;app \u00e8 vincolata a <code>127.0.0.1:3000<\/code> (non a <code>0.0.0.0<\/code>): questo impedisce connessioni dirette all&#8217;app bypassing il WAF. Solo NGINX, in esecuzione sullo stesso server, pu\u00f2 raggiungerla.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Installa le dipendenze e avvia l&#8217;app con PM2 per garantire il riavvio automatico:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install express\nnpm install -g pm2\n\npm2 start app.js --name \"node-app\"\npm2 startup\npm2 save<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-7-testare-la-configurazione-nginx\">Step 7: Testare la configurazione NGINX<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di riavviare NGINX, verifica la sintassi della configurazione. Qualsiasi errore qui impedirebbe il riavvio del server web:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo nginx -t\n# Output atteso:\n# nginx: the configuration file \/etc\/nginx\/nginx.conf syntax is ok\n# nginx: configuration file \/etc\/nginx\/nginx.conf test is successful\n\nsudo systemctl reload nginx<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema comune n. 3:<\/strong> Se vedi <code>unknown directive \"modsecurity\"<\/code>, il modulo non \u00e8 caricato correttamente. Verifica la riga <code>load_module<\/code> in <code>nginx.conf<\/code> e che il file <code>.so<\/code> esista:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ls -la \/usr\/lib\/nginx\/modules\/ngx_http_modsecurity_module.so\n# Deve esistere e avere permessi 0644<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-8-testare-il-waf-con-attacchi-simulati\">Step 8: Testare il WAF con attacchi simulati<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Con il WAF attivo, testa che blocchi correttamente i payload malevoli. Prima verifica che le richieste legittime passino:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Richiesta legittima: deve ritornare 200\ncurl -i \"https:\/\/example.com\/search?q=node.js\"\n\n# SQL injection: deve ritornare 403\ncurl -i \"https:\/\/example.com\/search?q=1%20OR%201=1\"\n\n# XSS: deve ritornare 403\ncurl -i \"https:\/\/example.com\/search?q=<script>alert('xss')<\/script>\"\n\n# Path traversal: deve ritornare 403\ncurl -i \"https:\/\/example.com\/search?file=..\/..\/etc\/passwd\"\n\n# Command injection: deve ritornare 403\ncurl -i \"https:\/\/example.com\/search?cmd=;cat%20\/etc\/shadow\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Per una verifica pi\u00f9 completa del WAF contro SQL injection, usa sqlmap in modalit\u00e0 non distruttiva (solo rilevamento, senza exploit):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Test sqlmap: verifica quante richieste vengono bloccate\nsqlmap -u \"https:\/\/example.com\/search?q=test\" \\\n  --batch \\\n  --level=3 \\\n  --risk=2 \\\n  --timeout=10 \\\n  2>&1 | tail -20<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con CRS al Paranoia Level 1, sqlmap dovrebbe rilevare il WAF e dichiarare il target protetto. Usa nikto per una scansione pi\u00f9 ampia delle vulnerabilit\u00e0 potenziali:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>nikto -h https:\/\/example.com -ssl -Format txt<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output tipico quando il WAF funziona: Nikto rilever\u00e0 il WAF e molti dei suoi test torneranno con 403 Forbidden invece dei codici che indicano vulnerabilit\u00e0.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-9-analizzare-i-log-di-audit-modsecurity\">Step 9: Analizzare i log di audit ModSecurity<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ogni richiesta bloccata viene registrata in formato JSON nel file di audit. Analizza i log per capire cosa viene bloccato e perch\u00e9:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Visualizza gli ultimi blocchi in formato leggibile\nsudo tail -f \/var\/log\/modsec_audit.log | python3 -m json.tool\n\n# Oppure con jq per un output pi\u00f9 compatto\nsudo tail -100 \/var\/log\/modsec_audit.log | \\\n  jq '{id:.transaction.id, ip:.transaction.client_ip, uri:.request.uri, rule:.matched_rules[0].id, msg:.matched_rules[0].message}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Un record di log tipico per un&#8217;iniezione SQL bloccata ha questa struttura:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"transaction\": {\n    \"id\": \"abc123def456\",\n    \"client_ip\": \"203.0.113.42\",\n    \"time\": \"2026-06-20T14:32:11+00:00\"\n  },\n  \"request\": {\n    \"method\": \"GET\",\n    \"uri\": \"\/search?q=1%20OR%201=1\",\n    \"headers\": { \"User-Agent\": \"curl\/8.x\" }\n  },\n  \"matched_rules\": [{\n    \"id\": \"942100\",\n    \"message\": \"SQL Injection Attack Detected via libinjection\",\n    \"severity\": \"CRITICAL\",\n    \"tags\": [\"attack-sqli\", \"OWASP_CRS\"]\n  }],\n  \"response\": {\n    \"status\": 403\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">I campi pi\u00f9 importanti da monitorare sono <code>matched_rules[].id<\/code> (l&#8217;ID della regola CRS che ha scattato) e <code>matched_rules[].message<\/code> (la descrizione della minaccia rilevata). Usa questi dati per identificare i pattern di attacco pi\u00f9 frequenti contro la tua app.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-10-ridurre-i-falsi-positivi-con-le-esclusioni\">Step 10: Ridurre i falsi positivi con le esclusioni<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il CRS pu\u00f2 bloccare traffico legittimo, specialmente con editor WYSIWYG, upload di file, o API che accettano input con caratteri speciali. La strategia corretta non \u00e8 abbassare il Paranoia Level, ma creare esclusioni mirate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Inizia sempre con <code>SecRuleEngine DetectionOnly<\/code> per una settimana, raccogliendo i log senza bloccare. Poi analizza i falsi positivi:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Conta quante volte scatta ogni regola (per identificare i falsi positivi pi\u00f9 frequenti)\nsudo cat \/var\/log\/modsec_audit.log | \\\n  python3 -c \"\nimport json, sys, collections\nrules = collections.Counter()\nfor line in sys.stdin:\n    try:\n        data = json.loads(line)\n        for rule in data.get('matched_rules', []):\n            rules[rule['id']] += 1\n    except: pass\nfor rule_id, count in rules.most_common(10):\n    print(f'Rule {rule_id}: {count} occorrenze')\n\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Per escludere una regola specifica solo per un determinato endpoint (ad esempio, un endpoint che accetta HTML da un editor), crea un file di esclusione:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \/etc\/nginx\/modsec\/exclusions.conf\n\n# Escludi le regole XSS solo per l'endpoint dell'editor (accetta HTML legittimo)\nSecRule REQUEST_URI \"@beginsWith \/api\/editor\" \\\n  \"id:10001, \\\n   phase:1, \\\n   pass, \\\n   nolog, \\\n   ctl:ruleRemoveByTag=attack-xss\"\n\n# Escludi un parametro specifico dalla regola 942100 (SQLi via libinjection)\n# solo se il tuo motore di ricerca accetta query avanzate con operatori\nSecRuleUpdateTargetById 942100 \"!ARGS:advanced_query\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aggiungi il file di esclusione nell&#8217;include di <code>modsecurity.conf<\/code>, dopo i CRS rules:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># In modsecurity.conf, dopo Include \/etc\/nginx\/owasp-crs\/rules\/*.conf\nInclude \/etc\/nginx\/modsec\/exclusions.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Regola d&#8217;oro:<\/strong> le esclusioni devono essere sempre il pi\u00f9 specifiche possibile. Escludere una regola per un intero dominio invece che per un singolo endpoint lascia buchi di sicurezza significativi. Usa sempre <code>REQUEST_URI \"@beginsWith \/percorso\/specifico\"<\/code> come condizione di partenza.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-11-deploy-con-docker-compose\">Step 11: Deploy con Docker Compose<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Per ambienti containerizzati, il pattern raccomandato separa NGINX+ModSecurity dall&#8217;app Node.js in container distinti, collegati da una rete interna Docker:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># docker-compose.yml\nversion: '3.8'\n\nservices:\n  waf:\n    image: nginx:1.24\n    ports:\n      - \"443:443\"\n      - \"80:80\"\n    volumes:\n      - .\/nginx\/nginx.conf:\/etc\/nginx\/nginx.conf:ro\n      - .\/nginx\/modsec:\/etc\/nginx\/modsec:ro\n      - .\/nginx\/owasp-crs:\/etc\/nginx\/owasp-crs:ro\n      - modsec_logs:\/var\/log\/modsecurity\n      - .\/certs:\/etc\/letsencrypt:ro\n    depends_on:\n      - app\n    networks:\n      - internal\n\n  app:\n    build: .\/node-app\n    expose:\n      - \"3000\"\n    environment:\n      - NODE_ENV=production\n    networks:\n      - internal\n    restart: unless-stopped\n\nvolumes:\n  modsec_logs:\n\nnetworks:\n  internal:\n    driver: bridge<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il container dell&#8217;app Node.js non espone nessuna porta all&#8217;host (usa <code>expose<\/code> invece di <code>ports<\/code>): \u00e8 accessibile solo dal container WAF tramite la rete interna. Questo garantisce che tutto il traffico passi obbligatoriamente per ModSecurity.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il <code>Dockerfile<\/code> per Node.js dovrebbe usare un&#8217;immagine base minimal e un utente non-root:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># node-app\/Dockerfile\nFROM node:22-alpine\n\nRUN addgroup -S appgroup && adduser -S appuser -G appgroup\n\nWORKDIR \/app\nCOPY package*.json .\/\nRUN npm ci --only=production\n\nCOPY --chown=appuser:appgroup . .\n\nUSER appuser\nEXPOSE 3000\n\nCMD [\"node\", \"app.js\"]<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-12-monitoraggio-alerting-e-aggiornamenti-crs\">Step 12: Monitoraggio, alerting e aggiornamenti CRS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un WAF non \u00e8 un firewall &#8220;configura e dimentica&#8221;: richiede monitoraggio continuo per essere efficace. Imposta un monitoraggio di base con logrotate per i log di audit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \/etc\/logrotate.d\/modsecurity\n\/var\/log\/modsec_audit.log {\n    daily\n    rotate 14\n    compress\n    delaycompress\n    missingok\n    notifempty\n    postrotate\n        nginx -s reopen\n    endscript\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Per ricevere alert in tempo reale sugli attacchi bloccati, usa questo script bash che controlla i log ogni minuto e invia una notifica se il numero di blocchi supera la soglia:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# \/opt\/waf-monitor.sh - Controlla attacchi nel minuto precedente\n\nTHRESHOLD=50  # Alert se pi\u00f9 di 50 blocchi in 1 minuto\nLOG=\"\/var\/log\/modsec_audit.log\"\nALERT_EMAIL=\"admin@example.com\"\n\nBLOCKS=$(sudo tail -1000 \"$LOG\" | \\\n  python3 -c \"\nimport json, sys\nfrom datetime import datetime, timedelta\ncutoff = datetime.utcnow() - timedelta(minutes=1)\ncount = 0\nfor line in sys.stdin:\n    try:\n        data = json.loads(line)\n        ts = data.get('transaction',{}).get('time','')\n        if ts and data.get('response',{}).get('status') == 403:\n            count += 1\n    except: pass\nprint(count)\n\")\n\nif [ \"$BLOCKS\" -gt \"$THRESHOLD\" ]; then\n  echo \"WAF Alert: $BLOCKS blocchi nell'ultimo minuto\" | \\\n    mail -s \"Alert WAF - Possibile attacco\" \"$ALERT_EMAIL\"\nfi<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aggiungi lo script al crontab per eseguirlo ogni minuto:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>chmod +x \/opt\/waf-monitor.sh\necho \"* * * * * root \/opt\/waf-monitor.sh\" | sudo tee \/etc\/cron.d\/waf-monitor<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aggiorna il CRS regolarmente. Il team OWASP rilascia aggiornamenti che coprono nuove CVE e vettori di attacco emergenti:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/etc\/nginx\/owasp-crs\nsudo git pull origin main\nsudo nginx -t && sudo systemctl reload nginx\necho \"CRS aggiornato il $(date)\"<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"errori-comuni-e-troubleshooting\">Errori comuni e troubleshooting<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Questi sono gli 8 problemi pi\u00f9 frequenti nell&#8217;installazione di ModSecurity e le relative soluzioni:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Errore<\/th><th>Causa<\/th><th>Soluzione<\/th><\/tr><\/thead><tbody><tr><td><code>unknown directive \"modsecurity\"<\/code><\/td><td>Modulo non caricato<\/td><td>Aggiungi <code>load_module modules\/ngx_http_modsecurity_module.so;<\/code> in nginx.conf<\/td><\/tr><tr><td>Errore ABI del modulo dinamico<\/td><td>Versione NGINX sorgente != installata<\/td><td>Usa esattamente la stessa versione NGINX per compilare il connettore<\/td><\/tr><tr><td>ModSecurity non blocca nulla<\/td><td><code>SecRuleEngine DetectionOnly<\/code><\/td><td>Cambia in <code>SecRuleEngine On<\/code><\/td><\/tr><tr><td>Richieste legittime bloccate (403)<\/td><td>Falsi positivi CRS<\/td><td>Analizza i log, crea esclusioni mirate per gli endpoint interessati<\/td><\/tr><tr><td><code>make: libyajl not found<\/code><\/td><td>Dipendenza mancante<\/td><td><code>sudo apt install libyajl-dev<\/code><\/td><\/tr><tr><td>Log di audit vuoti<\/td><td><code>SecAuditEngine Off<\/code><\/td><td>Imposta <code>SecAuditEngine RelevantOnly<\/code><\/td><\/tr><tr><td>Node.js vede IP 127.0.0.1<\/td><td>Proxy non configurato in Express<\/td><td><code>app.set('trust proxy', 1)<\/code><\/td><\/tr><tr><td>CRS non caricato<\/td><td>Path include errato<\/td><td>Verifica il percorso in modsecurity.conf, esegui <code>nginx -t<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema comune n. 4:<\/strong> il body JSON delle richieste POST non viene ispezionato. Assicurati che <code>SecRequestBodyAccess On<\/code> sia impostato e che il Content-Type sia tra quelli supportati (<code>application\/json<\/code>, <code>application\/x-www-form-urlencoded<\/code>, <code>multipart\/form-data<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Problema comune n. 5:<\/strong> file di upload rifiutati con 413 Request Entity Too Large. Il limite di default \u00e8 13 MB. Aumentalo nel file di configurazione:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># In modsecurity.conf\nSecRequestBodyLimit 52428800     # 50 MB\nSecRequestBodyNoFilesLimit 131072 # 128 KB per i campi non-file\n\n# In nginx.conf, nella sezione http o server\nclient_max_body_size 50m;<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"tecniche-di-bypass-waf-e-come-prevenirle\">Tecniche di bypass WAF e come prevenirle<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un WAF non \u00e8 impenetrabile. Gli attaccanti esperti usano tecniche di bypass specifiche. Conoscerle ti aiuta a rafforzare la configurazione:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Encoding multiplo:<\/strong> l&#8217;attaccante codifica il payload pi\u00f9 volte (URL encoding + base64 + HTML entities) sperando che il WAF decodifichi una volta sola. ModSecurity con CRS gestisce la decodifica multipla, ma assicurati che <code>SecRequestBodyAccess On<\/code> e la trasformazione <code>t:urlDecodeUni<\/code> siano attive nelle regole.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>HTTP Parameter Pollution:<\/strong> inviare lo stesso parametro pi\u00f9 volte con valori diversi, sperando che il WAF controlli solo la prima occorrenza mentre l&#8217;app usa l&#8217;ultima. NGINX\/ModSecurity gestisce questo unendo i valori, ma verifica il comportamento della tua app con parametri duplicati.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Chunked Transfer Encoding:<\/strong> inviare il corpo della richiesta in chunk. ModSecurity deve ricevere l&#8217;intero corpo prima di ispezionarlo. Verifica che <code>SecRequestBodyAccess On<\/code> sia attivo e che il buffer sia adeguato.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>JSON Nested Encoding:<\/strong> annidare il payload malevolo in strutture JSON profonde. Attiva <code>SecRule REQUEST_HEADERS:Content-Type<\/code> per ispezionare il tipo di contenuto e usa <code>@rx<\/code> per pattern matching sui body JSON.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Case Variation e Whitespace:<\/strong> usare <code>SeLeCt<\/code> invece di <code>SELECT<\/code>, o inserire commenti SQL <code>\/**\/<\/code> tra le parole chiave. L&#8217;OWASP CRS usa libinjection, una libreria specializzata nel rilevamento di SQL injection che \u00e8 resistente a queste variazioni per design.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"confronto-waf-modsecurity-vs-soluzioni-cloud\">Confronto WAF: ModSecurity vs. soluzioni cloud<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di impegnarsi su ModSecurity self-hosted, \u00e8 utile capire quando ha senso rispetto ai WAF cloud:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Soluzione WAF<\/th><th>Costo mensile<\/th><th>True Positive Rate<\/th><th>False Positive Rate<\/th><th>Controllo<\/th><\/tr><\/thead><tbody><tr><td>ModSecurity + CRS (self-hosted)<\/td><td>\u20ac0 (solo infrastruttura)<\/td><td>99,56% (profilo critico)<\/td><td>0,563%<\/td><td>Completo<\/td><\/tr><tr><td>Cloudflare WAF (Pro)<\/td><td>da $20\/mese<\/td><td>~97-98%<\/td><td>0,06%<\/td><td>Limitato<\/td><\/tr><tr><td>AWS WAF<\/td><td>da $5\/mese + $0,60\/1M req<\/td><td>97,5%<\/td><td>Variabile<\/td><td>Medio<\/td><\/tr><tr><td>Azure WAF<\/td><td>da \u20ac35\/mese<\/td><td>97,5%<\/td><td>Variabile<\/td><td>Medio<\/td><\/tr><tr><td>F5 NGINX App Protect<\/td><td>Commerciale, su licenza<\/td><td>97,8%<\/td><td>Basso<\/td><td>Alto<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">ModSecurity self-hosted \u00e8 la scelta giusta quando hai requisiti di conformit\u00e0 che richiedono che il traffico non lasci la tua infrastruttura (GDPR, NIS2), quando vuoi personalizzazione completa delle regole, o quando il volume di traffico renderebbe i WAF cloud costosi. I WAF cloud sono preferibili quando non hai personale per gestire aggiornamenti e tuning, o quando hai gi\u00e0 infrastruttura cloud con WAF integrato.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Per le PMI italiane soggette al <strong>GDPR<\/strong> e alla <strong>Direttiva NIS2<\/strong>, un WAF auto-ospitato offre anche un vantaggio di conformit\u00e0: nessun dato del traffico lascia il perimetro aziendale. Puoi approfondire le implicazioni normative nella nostra guida <a href=\"\/it\/direttiva-nis2-italia-2026\/\">NIS2 Italia 2026<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"suggerimenti-avanzati\">Suggerimenti avanzati<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>GeoIP blocking:<\/strong> ModSecurity supporta il blocco per paese tramite il database MaxMind GeoIP2. Utile per bloccare traffico da paesi senza utenti legittimi. Installa il modulo MaxMind e crea una regola:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SecGeoLookupDb \/etc\/nginx\/GeoIP\/GeoLite2-Country.mmdb\n\nSecRule GEO:COUNTRY_CODE \"@rx ^(CN|RU|KP)$\" \\\n  \"id:10100, \\\n   phase:1, \\\n   deny, \\\n   status:403, \\\n   log, \\\n   msg:'Blocked by country GeoIP'\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Rate limiting a livello WAF:<\/strong> combina ModSecurity con la direttiva <code>limit_req<\/code> di NGINX per un doppio strato di protezione contro brute force e DDoS applicativo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># In nginx.conf, nella sezione http\nlimit_req_zone $binary_remote_addr zone=api:10m rate=30r\/m;\n\n# Nel server block\nlocation \/api\/login {\n    limit_req zone=api burst=5 nodelay;\n    modsecurity on;\n    proxy_pass http:\/\/127.0.0.1:3000;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Regole CRS personalizzate per l&#8217;applicazione:<\/strong> dopo aver configurato CRS, aggiungi regole specifiche per la tua app che il CRS non copre. Ad esempio, blocca tentativi di accesso con credenziali note compromesse:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># \/etc\/nginx\/modsec\/custom-rules.conf\nSecRule REQUEST_URI \"@rx ^\/api\/login$\" \\\n  \"chain, id:20001, phase:2, deny, status:401, \\\n   log, msg:'Login con password nota compromessa'\"\n  SecRule REQUEST_BODY \"@rx \\\"password\\\":\\\"(123456|password|admin|qwerty)\\\"\" \"\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Integrazione con SIEM:<\/strong> esporta i log di ModSecurity verso Wazuh o Elasticsearch per la correlazione degli eventi. ModSecurity supporta l&#8217;output JSON che si integra nativamente con qualsiasi SIEM moderno. Consulta la nostra guida su <a href=\"\/it\/wazuh-tutorial-installazione-siem\/\">Wazuh: Installazione SIEM\/XDR<\/a> per il setup completo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Benchmark di performance:<\/strong> misura l&#8217;impatto del WAF sulla latenza con wrk prima e dopo l&#8217;attivazione:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Senza WAF (proxy diretto)\nwrk -t4 -c100 -d30s http:\/\/127.0.0.1:3000\/search?q=test\n\n# Con WAF (via NGINX+ModSecurity)\nwrk -t4 -c100 -d30s https:\/\/example.com\/search?q=test<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In produzione tipica, ModSecurity con CRS al Paranoia Level 1 aggiunge 2-5 ms di latenza per richiesta su hardware moderno. Con Paranoia Level 3-4, l&#8217;overhead pu\u00f2 salire a 10-20 ms per richieste con body complessi.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"copertura-correlata\">Copertura correlata<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Per approfondire la sicurezza del tuo stack Node.js, consulta questi articoli correlati:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/it\/xss-prevention-nodejs\/\">XSS in Node.js: Prevenirlo in 12 Step<\/a> \u2014 difesa in profondit\u00e0 lato applicativo contro il cross-site scripting<\/li>\n<li><a href=\"\/it\/sql-injection-nodejs\/\">SQL Injection in Node.js: Come Prevenirla in 12 Step<\/a> \u2014 parametrizzazione delle query e ORM sicuri<\/li>\n<li><a href=\"\/it\/validazione-input-nodejs\/\">Validazione Input con Zod, Joi e express-validator<\/a> \u2014 sanitizzazione e validazione a livello applicativo<\/li>\n<li><a href=\"\/it\/certbot-lets-encrypt-https\/\">Let&#8217;s Encrypt e Certbot: HTTPS Gratis in 10 Step<\/a> \u2014 certificati TLS per il server NGINX davanti al WAF<\/li>\n<li><a href=\"\/it\/openssl-certificati-chiavi\/\">OpenSSL 3.5 LTS: Chiavi e Certificati in 12 Step<\/a> \u2014 gestione di certificati X.509 per ambienti enterprise<\/li>\n<li><a href=\"\/it\/wazuh-tutorial-installazione-siem\/\">Wazuh: Installazione e Configurazione SIEM\/XDR<\/a> \u2014 integra i log WAF in un sistema SIEM completo<\/li>\n<li><a href=\"\/it\/nessus-vs-openvas-scanner-vulnerabilita\/\">Nessus vs OpenVAS: Scanner di Vulnerabilit\u00e0<\/a> \u2014 scansione proattiva delle vulnerabilit\u00e0<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faq-web-application-firewall-con-modsecurity\">FAQ: Web Application Firewall con ModSecurity<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"modsecurity-3-x-e-stabile-per-la-produzione\">ModSecurity 3.x \u00e8 stabile per la produzione?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">S\u00ec. ModSecurity 3.x (ramo v3\/master) \u00e8 ampiamente usato in produzione dal 2018. Il ramo v2 \u00e8 ancora in uso ma non riceve nuove funzionalit\u00e0. Per nuove installazioni con NGINX, usa sempre v3.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quale-paranoia-level-devo-usare\">Quale Paranoia Level devo usare?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Parti dal livello 1 per almeno due settimane in modalit\u00e0 <code>DetectionOnly<\/code>. Analizza i falsi positivi, crea le esclusioni necessarie, poi passa a <code>SecRuleEngine On<\/code>. Aumenta al livello 2 solo dopo aver stabilizzato il livello 1. La maggior parte delle app in produzione funziona bene al livello 1 o 2.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"il-waf-protegge-dalle-vulnerabilita-nelle-dipendenze-npm\">Il WAF protegge dalle vulnerabilit\u00e0 nelle dipendenze npm?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Parzialmente. Il WAF pu\u00f2 bloccare i payload di exploit noti che arrivano via HTTP (come exploit di Log4Shell o SSRF). Non pu\u00f2 proteggere da vulnerabilit\u00e0 che si manifestano internamente senza payload HTTP specifici. Per la sicurezza delle dipendenze npm, usa <a href=\"\/it\/npm-supply-chain-attacks-2026\/\">npm audit<\/a> e strumenti di Software Composition Analysis (SCA).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"devo-usare-un-waf-se-uso-gia-cloudflare\">Devo usare un WAF se uso gi\u00e0 Cloudflare?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La difesa in profondit\u00e0 (defense in depth) raccomanda entrambi i livelli: Cloudflare WAF davanti per il traffico globale e ModSecurity sul server per un secondo strato di protezione. Se un attaccante bypassa Cloudflare (cambio DNS, leak del tuo IP reale), ModSecurity \u00e8 l&#8217;ultima linea di difesa.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"come-aggiorno-le-regole-crs-senza-downtime\">Come aggiorno le regole CRS senza downtime?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Con <code>nginx -s reload<\/code> invece di <code>systemctl restart nginx<\/code>: NGINX esegue un graceful reload, finendo di servire le richieste in corso prima di applicare la nuova configurazione. Automatizza l&#8217;aggiornamento con un cron job settimanale che esegue <code>git pull<\/code> nel repository CRS seguito da <code>nginx -t && nginx -s reload<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"modsecurity-puo-gestire-websocket\">ModSecurity pu\u00f2 gestire WebSocket?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. ModSecurity ispeziona solo il traffico HTTP\/HTTPS standard. Le connessioni WebSocket (dopo l&#8217;handshake HTTP Upgrade) non vengono ispezionate. Per proteggere i WebSocket, implementa validazione e autenticazione a livello applicativo. Separa gli endpoint WebSocket dagli endpoint REST nel reverse proxy NGINX.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quanto-costa-gestire-modsecurity-in-produzione\">Quanto costa gestire ModSecurity in produzione?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Il software \u00e8 gratuito. Il costo \u00e8 operativo: 2-4 ore\/mese per aggiornamenti CRS, revisione log e tuning dei falsi positivi. Rispetto ai costi di un cloud WAF commerciale (da $20 a $400\/mese per traffico significativo), il break-even \u00e8 raggiunto in 2-3 mesi anche considerando il tempo del sistemista.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"il-waf-e-sufficiente-per-la-conformita-nis2\">Il WAF \u00e8 sufficiente per la conformit\u00e0 NIS2?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Il WAF \u00e8 uno degli strumenti richiesti dalla NIS2 per la gestione del rischio tecnico, ma non \u00e8 sufficiente da solo. La NIS2 richiede anche incident response, gestione delle vulnerabilit\u00e0, controllo degli accessi e MFA, backup sicuri e formazione del personale. Il WAF copre il requisito di &#8220;misure tecniche per prevenire gli attacchi HTTP&#8221;.<\/p>\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"progetto-completo-stack-waf-production-ready\">Progetto completo: stack WAF production-ready<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Qui sotto trovi la struttura completa di un progetto Node.js protetto da WAF, pronto per essere deployato in produzione. Organizza la directory del progetto in questo modo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>my-app\/\n\u251c\u2500\u2500 docker-compose.yml\n\u251c\u2500\u2500 docker-compose.prod.yml\n\u251c\u2500\u2500 nginx\/\n\u2502   \u251c\u2500\u2500 nginx.conf\n\u2502   \u251c\u2500\u2500 sites-enabled\/\n\u2502   \u2502   \u2514\u2500\u2500 app.conf\n\u2502   \u2514\u2500\u2500 modsec\/\n\u2502       \u251c\u2500\u2500 modsecurity.conf\n\u2502       \u251c\u2500\u2500 crs-setup.conf\n\u2502       \u2514\u2500\u2500 exclusions.conf\n\u251c\u2500\u2500 node-app\/\n\u2502   \u251c\u2500\u2500 Dockerfile\n\u2502   \u251c\u2500\u2500 package.json\n\u2502   \u251c\u2500\u2500 app.js\n\u2502   \u2514\u2500\u2500 routes\/\n\u2502       \u251c\u2500\u2500 auth.js\n\u2502       \u2514\u2500\u2500 api.js\n\u251c\u2500\u2500 certs\/\n\u2502   \u2514\u2500\u2500 .gitkeep\n\u2514\u2500\u2500 logs\/\n    \u2514\u2500\u2500 .gitkeep<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il file <code>nginx.conf<\/code> completo con tutte le impostazioni di sicurezza consigliate:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># nginx\/nginx.conf\nload_module modules\/ngx_http_modsecurity_module.so;\n\nuser nginx;\nworker_processes auto;\nworker_rlimit_nofile 65535;\nerror_log \/var\/log\/nginx\/error.log warn;\n\nevents {\n    worker_connections 4096;\n    use epoll;\n    multi_accept on;\n}\n\nhttp {\n    include \/etc\/nginx\/mime.types;\n    default_type application\/octet-stream;\n\n    # Header di sicurezza globali\n    add_header X-Frame-Options \"SAMEORIGIN\" always;\n    add_header X-Content-Type-Options \"nosniff\" always;\n    add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n    add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;\n\n    # Nascondi la versione NGINX\n    server_tokens off;\n\n    # Limiti di dimensione\n    client_max_body_size 10m;\n    client_body_timeout 12;\n    client_header_timeout 12;\n    send_timeout 10;\n\n    # Compressione\n    gzip on;\n    gzip_comp_level 5;\n    gzip_types text\/plain application\/json text\/css;\n\n    # Rate limiting zone\n    limit_req_zone $binary_remote_addr zone=general:10m rate=60r\/m;\n    limit_req_zone $binary_remote_addr zone=auth:10m rate=5r\/m;\n    limit_conn_zone $binary_remote_addr zone=connections:10m;\n\n    # Log formato JSON per compatibilit\u00e0 SIEM\n    log_format json_combined escape=json\n      '{\"time\":\"$time_iso8601\",'\n      '\"remote_addr\":\"$remote_addr\",'\n      '\"method\":\"$request_method\",'\n      '\"uri\":\"$request_uri\",'\n      '\"status\":$status,'\n      '\"bytes_sent\":$bytes_sent,'\n      '\"request_time\":$request_time,'\n      '\"user_agent\":\"$http_user_agent\"}';\n\n    access_log \/var\/log\/nginx\/access.log json_combined;\n\n    include \/etc\/nginx\/sites-enabled\/*.conf;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il file <code>sites-enabled\/app.conf<\/code> con la configurazione del virtual host e il WAF abilitato:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># nginx\/sites-enabled\/app.conf\nupstream nodejs_backend {\n    server app:3000;\n    keepalive 32;\n}\n\nserver {\n    listen 80;\n    server_name example.com www.example.com;\n    return 301 https:\/\/$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    server_name example.com www.example.com;\n\n    # SSL\/TLS\n    ssl_certificate \/etc\/letsencrypt\/live\/example.com\/fullchain.pem;\n    ssl_certificate_key \/etc\/letsencrypt\/live\/example.com\/privkey.pem;\n    ssl_session_timeout 1d;\n    ssl_session_cache shared:MozSSL:10m;\n    ssl_session_tickets off;\n    ssl_protocols TLSv1.2 TLSv1.3;\n    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;\n    ssl_prefer_server_ciphers off;\n    add_header Strict-Transport-Security \"max-age=63072000\" always;\n\n    # ModSecurity\n    modsecurity on;\n    modsecurity_rules_file \/etc\/nginx\/modsec\/modsecurity.conf;\n\n    # Rate limiting per gli endpoint critici\n    location \/api\/auth\/ {\n        limit_req zone=auth burst=3 nodelay;\n        limit_conn connections 3;\n        proxy_pass http:\/\/nodejs_backend;\n        proxy_http_version 1.1;\n        proxy_set_header Connection \"\";\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    location \/ {\n        limit_req zone=general burst=20 nodelay;\n        proxy_pass http:\/\/nodejs_backend;\n        proxy_http_version 1.1;\n        proxy_set_header Connection \"\";\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;app Node.js completa con route di autenticazione e API, pronta per il deploy dietro il WAF:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ node-app\/app.js\n'use strict';\nconst express = require('express');\nconst helmet = require('helmet');\nconst { body, query, validationResult } = require('express-validator');\n\nconst app = express();\n\n\/\/ Fiducia nel reverse proxy NGINX\napp.set('trust proxy', 1);\n\n\/\/ Middleware di sicurezza (strato applicativo, in aggiunta al WAF)\napp.use(helmet({\n  contentSecurityPolicy: {\n    directives: {\n      defaultSrc: [\"'self'\"],\n      scriptSrc: [\"'self'\"],\n      styleSrc: [\"'self'\"],\n      imgSrc: [\"'self'\", 'data:'],\n    }\n  },\n  hsts: false, \/\/ Gestito da NGINX\n}));\n\napp.use(express.json({ limit: '1mb' }));\napp.use(express.urlencoded({ extended: true, limit: '1mb' }));\n\n\/\/ Health check (non protetto da rate limiting in NGINX)\napp.get('\/health', (req, res) => {\n  res.json({ status: 'ok', timestamp: new Date().toISOString() });\n});\n\n\/\/ Ricerca con validazione input\napp.get('\/search',\n  query('q')\n    .trim()\n    .isLength({ min: 1, max: 200 })\n    .escape(),\n  (req, res) => {\n    const errors = validationResult(req);\n    if (!errors.isEmpty()) {\n      return res.status(400).json({ errors: errors.array() });\n    }\n    const { q } = req.query;\n    res.json({ query: q, results: [], clientIP: req.ip });\n  }\n);\n\n\/\/ Route API\napp.use('\/api', require('.\/routes\/api'));\napp.use('\/api\/auth', require('.\/routes\/auth'));\n\n\/\/ Error handler\napp.use((err, req, res, next) => {\n  console.error(err.stack);\n  res.status(500).json({ error: 'Internal server error' });\n});\n\nconst PORT = process.env.PORT || 3000;\napp.listen(PORT, '0.0.0.0', () => {\n  console.log(`App in ascolto sulla porta ${PORT}`);\n});\n\nmodule.exports = app;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nota come l&#8217;app usa helmet per aggiungere un secondo strato di header di sicurezza (Content-Security-Policy, X-Frame-Options, ecc.) anche a livello applicativo. Questo segue il principio della difesa in profondit\u00e0: il WAF blocca i payload malevoli, helmet protegge dai misuse del browser, la validazione input (express-validator) garantisce la correttezza dei dati prima che raggiungano la business logic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Per installare le dipendenze dell&#8217;app Node.js:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd node-app\nnpm install express helmet express-validator\nnpm install --save-dev jest supertest<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"test-automatici-per-verificare-il-waf\">Test automatici per verificare il WAF<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Automatizza il test del WAF con uno script che verifica i casi d&#8217;uso pi\u00f9 importanti. Questo script pu\u00f2 essere integrato nella CI\/CD pipeline per garantire che il WAF sia attivo e funzionante prima di ogni deployment:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n# waf-smoke-test.sh - Verifica che il WAF blocchi gli attacchi noti\n\nBASE_URL=\"${1:-https:\/\/example.com}\"\nPASS=0\nFAIL=0\n\ncheck() {\n  local description=\"$1\"\n  local url=\"$2\"\n  local expected_status=\"$3\"\n  local method=\"${4:-GET}\"\n  local body=\"$5\"\n\n  if [ \"$method\" = \"POST\" ]; then\n    actual=$(curl -s -o \/dev\/null -w '%{http_code}' \\\n      -X POST -H 'Content-Type: application\/json' \\\n      -d \"$body\" \"$url\")\n  else\n    actual=$(curl -s -o \/dev\/null -w '%{http_code}' \"$url\")\n  fi\n\n  if [ \"$actual\" = \"$expected_status\" ]; then\n    echo \"PASS | $description (HTTP $actual)\"\n    PASS=$((PASS + 1))\n  else\n    echo \"FAIL | $description (atteso $expected_status, ottenuto $actual)\"\n    FAIL=$((FAIL + 1))\n  fi\n}\n\necho \"=== WAF Smoke Test: $BASE_URL ===\"\n\n# Traffico legittimo: deve passare\ncheck \"Richiesta GET normale\" \"$BASE_URL\/search?q=nodejs\" \"200\"\ncheck \"Health check\" \"$BASE_URL\/health\" \"200\"\n\n# SQL Injection: deve essere bloccata (403)\ncheck \"SQL injection GET param\" \\\n  \"$BASE_URL\/search?q=1%20OR%201=1--\" \"403\"\ncheck \"SQL injection UNION\" \\\n  \"$BASE_URL\/search?q=1%20UNION%20SELECT%20null,null--\" \"403\"\ncheck \"SQL injection blind\" \\\n  \"$BASE_URL\/search?q=1%27%20AND%20sleep(5)--\" \"403\"\n\n# XSS: deve essere bloccata (403)\ncheck \"XSS script tag\" \\\n  \"$BASE_URL\/search?q=%3Cscript%3Ealert(1)%3C\/script%3E\" \"403\"\ncheck \"XSS evento DOM\" \\\n  \"$BASE_URL\/search?q=%22%3E%3Cimg%20onerror%3Dalert(1)%3E\" \"403\"\n\n# Path traversal: deve essere bloccata (403)\ncheck \"Path traversal Unix\" \\\n  \"$BASE_URL\/search?file=..\/..\/etc\/passwd\" \"403\"\ncheck \"Path traversal encoded\" \\\n  \"$BASE_URL\/search?file=%2e%2e%2f%2e%2e%2fetc%2fpasswd\" \"403\"\n\n# Command injection: deve essere bloccata (403)\ncheck \"Command injection\" \\\n  \"$BASE_URL\/search?q=;cat%20\/etc\/shadow\" \"403\"\n\necho \"\"\necho \"=== Risultati: $PASS PASS, $FAIL FAIL ===\"\n[ \"$FAIL\" -eq 0 ] && echo \"WAF OK: tutti i test superati.\" || echo \"WAF WARNING: $FAIL test falliti.\"\nexit \"$FAIL\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esegui lo script come parte della pipeline di deployment:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>chmod +x waf-smoke-test.sh\n.\/waf-smoke-test.sh https:\/\/example.com\n\n# Output atteso:\n# PASS | Richiesta GET normale (HTTP 200)\n# PASS | Health check (HTTP 200)\n# PASS | SQL injection GET param (HTTP 403)\n# PASS | SQL injection UNION (HTTP 403)\n# PASS | SQL injection blind (HTTP 403)\n# PASS | XSS script tag (HTTP 403)\n# PASS | XSS evento DOM (HTTP 403)\n# PASS | Path traversal Unix (HTTP 403)\n# PASS | Path traversal encoded (HTTP 403)\n# PASS | Command injection (HTTP 403)\n# \n# Risultati: 10 PASS, 0 FAIL\n# WAF OK: tutti i test superati.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Integra questo test in GitHub Actions o GitLab CI aggiungendo un job di smoke test post-deployment che fallisce il pipeline se il WAF non blocca i payload critici. Un WAF non funzionante \u00e8 peggio di nessun WAF: crea una falsa sensazione di sicurezza.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"performance-e-tuning-avanzato\">Performance e tuning avanzato<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ModSecurity con CRS aggiunge overhead di CPU e latenza. Ecco le principali ottimizzazioni per ambienti ad alto traffico:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Disabilita l&#8217;ispezione del body in risposta:<\/strong> in quasi tutti i casi, ispezionare le risposte non aggiunge valore significativo ma aumenta l&#8217;overhead. Mantieni <code>SecResponseBodyAccess Off<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Usa <code>SecRule<\/code> con tag specifici per escludere endpoint interni:<\/strong> gli endpoint di health check, metrics e admin (accessibili solo da IP interni) non hanno bisogno di essere ispezionati dal WAF. Escludili esplicitamente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Escludi il health check dal WAF (chiamato solo da load balancer interni)\nSecRule REMOTE_ADDR \"@ipMatch 10.0.0.0\/8,172.16.0.0\/12,192.168.0.0\/16\" \\\n  \"id:10200, phase:1, pass, nolog, \\\n   ctl:ruleEngine=Off\" \n\n# Oppure esclusione per URI\nSecRule REQUEST_URI \"@rx ^\/(health|metrics|readyz)$\" \\\n  \"id:10201, phase:1, pass, nolog, \\\n   ctl:ruleEngine=Off\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Limite del body PCRE:<\/strong> le espressioni regolari PCRE usate da CRS possono impazzire su input molto grandi con strutture di matching complesse (ReDoS). Il parametro <code>SecPcreMatchLimit<\/code> limita il numero di operazioni PCRE per prevenire blocchi del worker process:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># In modsecurity.conf\nSecPcreMatchLimit 500000\nSecPcreMatchLimitRecursion 500000<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Worker processes e buffer:<\/strong> NGINX dovrebbe avere un numero di worker process uguale al numero di CPU core, con buffer di proxy ottimizzati per il traffico tipico:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># In nginx.conf - sezione http\nproxy_buffering on;\nproxy_buffer_size 4k;\nproxy_buffers 8 4k;\nproxy_busy_buffers_size 8k;\nproxy_temp_file_write_size 8k;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Per applicazioni ad alto volume (oltre 1.000 req\/s), considera l&#8217;utilizzo di pi\u00f9 worker NGINX con il modulo di bilanciamento del carico davanti a pi\u00f9 istanze Node.js. Il WAF rimane un singolo punto, ma NGINX gestisce efficientemente il proxy verso un upstream pool:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>upstream nodejs_cluster {\n    least_conn;\n    server 127.0.0.1:3000;\n    server 127.0.0.1:3001;\n    server 127.0.0.1:3002;\n    server 127.0.0.1:3003;\n    keepalive 64;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Con quattro istanze Node.js e NGINX + ModSecurity, un server con 4 core e 8 GB RAM gestisce tipicamente 800-1.200 req\/s con payload REST di piccole dimensioni e CRS al Paranoia Level 1. Per traffico superiore, la soluzione corretta \u00e8 scalare orizzontalmente con un load balancer davanti a pi\u00f9 server NGINX+WAF.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conformita-nis2-e-gdpr-con-il-waf\">Conformit\u00e0 NIS2 e GDPR con il WAF<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Per le aziende italiane soggette alla <strong>Direttiva NIS2<\/strong> (recepita in Italia con il D.Lgs. 138\/2024) e al <strong>GDPR<\/strong>, l&#8217;implementazione di un WAF contribuisce a soddisfare specifici requisiti normativi. L&#8217;articolo 21 della NIS2 richiede misure tecniche appropriate per gestire i rischi per la sicurezza, tra cui la protezione delle applicazioni web.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il WAF con ModSecurity e CRS documenta automaticamente ogni tentativo di attacco bloccato nel log di audit. Questi log sono preziosi in caso di audit NIS2 o in caso di notifica di incidente: dimostrano che l&#8217;azienda ha implementato controlli tecnici adeguati. Conserva i log per almeno 12 mesi (NIS2 richiede la tracciabilit\u00e0 degli incidenti):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Logrotate per conservare i log 12 mesi (90 giorni * 4 rotazioni = ~1 anno)\n\/var\/log\/modsec_audit.log {\n    daily\n    rotate 365\n    compress\n    dateext\n    dateformat -%Y%m%d\n    missingok\n    notifempty\n    postrotate\n        nginx -s reopen 2>\/dev\/null || true\n    endscript\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Per la conformit\u00e0 GDPR, fai attenzione ai dati personali che potrebbero finire nei log di audit ModSecurity: indirizzi email, nomi utente, token JWT o altri dati personali inclusi nei parametri delle richieste. Configura ModSecurity per anonimizzare gli IP nei log se richiesto dalla tua policy sulla privacy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Anonimizza l'ultimo ottetto dell'IP nei log\n# (aggiunge una regola di trasformazione prima del logging)\nSecRule REMOTE_ADDR \"@rx ^(\\d+\\.\\d+\\.\\d+)\\.\\d+$\" \\\n  \"id:10300, phase:5, pass, nolog, \\\n   setenv:ANON_IP=%{TX.1}.0\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Considera anche che i log di sicurezza sono considerati dati personali ai sensi del GDPR se consentono di identificare gli individui tramite l&#8217;indirizzo IP. Documenta nel Registro dei Trattamenti il trattamento dei log WAF, la base giuridica (interesse legittimo per la sicurezza informatica) e il periodo di conservazione.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Per risorse aggiuntive sulle normative di sicurezza italiane ed europee, consulta la nostra guida su <a href=\"\/it\/direttiva-nis2-italia-2026\/\">NIS2 Italia 2026<\/a> e il confronto <a href=\"\/it\/nis2-vs-dora-confronto-2026\/\">NIS2 vs DORA<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Un Web Application Firewall (WAF) con ModSecurity e NGINX \u00e8 il modo pi\u00f9 robusto per proteggere un&#8217;applicazione Node.js dagli attacchi pi\u00f9 comuni: SQL injection, cross-site scripting, path traversal e centinaia\u2026<\/p>\n","protected":false},"author":6,"featured_media":275,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-274","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security"],"_links":{"self":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/274","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/comments?post=274"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/274\/revisions"}],"predecessor-version":[{"id":276,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/274\/revisions\/276"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media\/275"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media?parent=274"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/categories?post=274"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/tags?post=274"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}