Skip to main content
  1. Blog/

Il Kernel Sospende il Giudizio

Alessio Barnini
Author
Alessio Barnini
Table of Contents

TL;DR
  • IDS (af-packet): copia del traffico → Suricata vede tutto, non può bloccare niente → [**] in fast.log
  • IPS (nfqueue): traffico originale trattenuto → il kernel aspetta il verdetto → [Drop] in fast.log
  • iptables -I INPUT -j NFQUEUE --queue-num 0 è la singola regola che trasforma il sistema
  • fail-open: no = fail-closed: se Suricata muore, tutto il traffico viene droppato
  • Il Docker bridge (br-XXXX) bypassa NFQUEUE - la SYN-ACK di ritorno viene bloccata e Wazuh si disconnette
  • La persistenza al reboot richiede un systemd service dedicato (non iptables-persistent, che rimuove UFW)
$ history
  • suricata -T -c /etc/suricata/suricata.yaml -v
  • suricata-update
  • iptables -I INPUT 1 -j NFQUEUE --queue-num 0
  • iptables -L INPUT -n -v --line-numbers
  • nmap --min-rate 1000 -p 1-1000 192.168.64.3
  • hping3 -S --flood 192.168.64.3
  • cat /proc/net/tcp

Voglio vedere cosa succede quando Suricata non si limita a guardare il traffico, ma lo blocca davvero.

Ho già Suricata 7.0.3 installato in modalità IDS - riceve una copia di ogni pacchetto, analizza, logga. Ma il traffico passa lo stesso. Un attaccante che fa port scan lo vedo in fast.log, ma non posso farci niente.

Oggi cambia.


Il setup
#

Mac (192.168.64.1)     → gateway / management
Ubuntu (192.168.64.3)  → defender: Suricata + Wazuh in Docker
Kali (192.168.64.200)  → attaccante

Ubuntu ha già Suricata 7.0.3 e Wazuh 4.14.5 in Docker (tre container: manager, indexer, dashboard). Wazuh legge l'eve.json di Suricata in tempo reale.


IDS vs NIDS: prima di toccare la configurazione
#

Ho già Wazuh con il suo agent (wazuh-agent.service) che monitora l'host. Che senso ha aggiungere Suricata?

La distinzione è fondamentale per Security+ e per capire cosa stai costruendo:

┌─────────────────────────────────────────────────────────────┐
│                         HIDS                                │
(Host-based Intrusion Detection)│                                                             │
│   Wazuh agent gira DENTRO il sistema operativo              │
│   Legge: auth.log, /etc/passwd, modifiche ai file           │
│   Vede: "qualcuno ha aperto questo file alle 03:00"│                                                             │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                         NIDS                                │
(Network-based Intrusion Detection)│                                                             │
│   Suricata gira DAVANTI al sistema operativo                │
│   Legge: pacchetti di rete grezzi sull'interfaccia          │
│   Vede: "qualcuno sta mandando 1000 SYN al secondo"│                                                             │
└─────────────────────────────────────────────────────────────┘

Non si sostituiscono. Si completano. Un port scan non lascia traccia in auth.log. Una modifica a /etc/passwd non è visibile nei pacchetti di rete. Sono layer diversi dello stesso problema.


IDS vs IPS: la distinzione che conta per l'esame
#

IDS = Intrusion Detection System   → rileva, non blocca
IPS = Intrusion Prevention System  → rileva E blocca

Non sono prodotti diversi. È la stessa tecnologia - Suricata può fare entrambi - con una differenza di posizionamento.

L'analogia che funziona meglio: una telecamera di sorveglianza vs un tornello.

La telecamera (IDS) vede tutto. Registra tutto. Manda alert al centro di controllo. Ma il ladro attraversa l'ingresso lo stesso - la telecamera non può fermarlo fisicamente. L'analisi avviene dopo.

Il tornello (IPS) è nel percorso. Non puoi passare senza che lui decida. Se non hai il badge giusto, non passi. La decisione avviene prima che tu entri.

Confronto concettuale tra posizionamento IDS (fuori percorso) e IPS (in linea)

graph LR
    subgraph IDS["IDS - Suricata af-packet"]
        direction TB
        P1[Pacchetto entra] --> K1[Kernel lo accetta]
        K1 --> APP1[Applicazione lo riceve]
        P1 -.->|copia| S1[Suricata analizza]
        S1 --> L1[fast.log: alert ✅]
    end

    subgraph IPS["IPS - Suricata nfqueue"]
        direction TB
        P2[Pacchetto entra] --> NF[NFQUEUE: kernel lo trattiene]
        NF --> S2[Suricata analizza]
        S2 -->|ACCEPT| APP2[Applicazione lo riceve]
        S2 -->|DROP| TRASH[Scartato ❌]
        S2 --> L2[fast.log: Drop ✅]
    end

    style IDS fill:none,stroke:none
    style IPS fill:none,stroke:none

La differenza visibile: in fast.log, IDS produce [**], IPS produce [Drop]. In nmap, IDS mostra porte open/closed, IPS mostra porte filtered.


Fase 1: baseline IDS
#

Suricata gira già in modalità af-packet. Verifico:

sudo systemctl status suricata
grep "af-packet" /etc/suricata/suricata.yaml

Lancio nmap da Kali:

sudo nmap -sS 192.168.64.3

Su Ubuntu:

sudo tail -f /var/log/suricata/fast.log
05/28/2026  [**] [1:2010936:3] ET SCAN Nmap -sS window 1024 [**]
             [**] = alert, non drop. Il traffico è passato.

nmap su Kali vede le porte normalmente. Questo è il baseline: Suricata vede, non agisce.


Il meccanismo NFQUEUE: il kernel aspetta
#

La differenza tecnica tra af-packet e nfqueue è nel punto di intercettazione.

af-packet (IDS):

  [rete] ──► [kernel] ──► [applicazione]
                └── copia ──► [Suricata]
                               (fuori dal percorso)


nfqueue (IPS):

  [rete] ──► [kernel] ──► [NFQUEUE] ──► [Suricata]
                               │              │
                               │         ACCEPT │ DROP
                               │              │
                               └──────────────┘
                               [applicazione]
                               oppure [scartato]

Con NFQUEUE, il kernel trattiene ogni pacchetto in una coda numerata (--queue-num 0). Suricata legge dalla coda, analizza, e manda un verdetto: NF_ACCEPT o NF_DROP. Il kernel esegue.

Finché Suricata non risponde, il pacchetto non si muove.

È come la dogana. Non è una telecamera che fotografa quello che entra - è un funzionario che trattiene il pacco, lo apre, decide, e solo allora lo consegna o lo confisca.


La configurazione: primo errore
#

Modifico /etc/suricata/suricata.yaml con vim per passare a nfqueue.

Commento la sezione af-packet e abilito nfq:

nfq:
  mode: accept
  repeat-mark: 1
  repeat-mask: 1
  fail-open: no

Una nota su fail-open: no - ci torno dopo. Per ora: questa scelta ha conseguenze importanti.

Riavvio Suricata:

May 28 15:44:50 suricata: <Error> eth0: No such device

L'interfaccia si chiama eth0 nel yaml di default. Su Ubuntu ARM su UTM si chiama enp0s1 - il naming legacy ethX è stato sostituito da naming predittivo basato su posizione del bus. Lo verifico:

ip link show
# 2: enp0s1: <BROADCAST,MULTICAST,UP,LOWER_UP>

Apro il yaml con vim, cerco eth0, correggo. Niente sed su file di configurazione complessi, meglio vedere il contesto prima di modificare.

sudo systemctl restart suricata
sudo systemctl status suricata

Questa volta parte.


Zero regole
#

sudo tail -20 /var/log/suricata/suricata.log
# [WARN] - No rules loaded!

Suricata gira ma non ha regole. Senza regole drop, ogni pacchetto riceve ACCEPT di default - l'IPS è inline ma non blocca niente.

suricata-update scarica l'Emerging Threats Open ruleset:

sudo suricata-update
# Loaded 50241 rules

50.241 regole. Adesso Suricata ha qualcosa da fare.


fast.log ed eve.json
#

Suricata scrive in due file distinti, entrambi in /var/log/suricata/. Esistono per scopi diversi e si usano in momenti diversi.

/var/log/suricata/fast.log - formato testuale, leggibile a occhio, pensato per il monitoring diretto in terminale:

sudo tail -f /var/log/suricata/fast.log
[**] [1:2010936:3] ET SCAN Nmap -sS window 1024  ← IDS: alert
[Drop] [1:9000002:1] IPS Aggressive Port Scan     ← IPS: blocco

Utile quando sei davanti al terminale e vuoi vedere gli alert in tempo reale. Non è strutturato, non è parsabile automaticamente.

/var/log/suricata/eve.json - formato JSON strutturato, una riga per evento, progettato per essere letto da SIEM e strumenti di analisi:

sudo tail -f /var/log/suricata/eve.json | python3 -m json.tool
{
  "timestamp": "2026-05-29T07:29:02",
  "event_type": "alert",
  "src_ip": "192.168.64.200",
  "dest_ip": "192.168.64.3",
  "alert": {
    "action": "blocked",
    "signature": "IPS Aggressive Port Scan Drop",
    "severity": 3
  }
}

EVE sta per Extensible Event Format, il formato JSON standard di Suricata per l'output strutturato. "Extensible" perché ogni tipo di evento (alert, flow, DNS, TLS, HTTP) ha i suoi campi aggiuntivi nello stesso formato base.

Wazuh non legge fast.log. Legge eve.json. Il Wazuh agent ha una voce localfile in ossec.conf che punta a quel file, lo legge riga per riga, e manda ogni evento JSON al manager in tempo reale. Il manager lo decodifica e applica le sue regole di correlazione.

flowchart LR
    S[Suricata] --> F[fast.log\ntestuale]
    S --> E[eve.json\nJSON strutturato]
    E --> WA[Wazuh Agent]
    WA --> WM[Wazuh Manager]
    WM --> WD[Dashboard]
    F --> OP[Operatore\nmonitoraggio diretto]

In IDS, eve.json mostra "action": "allowed". In IPS, mostra "action": "blocked". Questa è la differenza che appare nel Wazuh dashboard.


Il servizio systemd: due trappole
#

Suricata in modalità nfqueue si avvia con -q 0 - il numero della coda netfilter. Il servizio systemd di default usa --pcap per af-packet.

Creo l'override:

sudo mkdir -p /etc/systemd/system/suricata.service.d/
sudo tee /etc/systemd/system/suricata.service.d/override.conf << 'EOF'
[Service]
Type=simple
ExecStart=
ExecStart=/usr/bin/suricata -c /etc/suricata/suricata.yaml -q 0 --pidfile /run/suricata.pid
EOF

Trappola 1: ExecStart= vuoto prima del nuovo valore. In systemd, per sovrascrivere (non aggiungere) un ExecStart esistente, bisogna prima svuotarlo. Senza quella riga vuota, il secondo ExecStart si accumula al precedente.

Trappola 2: Type=simple invece di Type=notify. Suricata in nfqueue non manda il segnale sd_notify. Con Type=notify, systemd aspetta quel segnale per 90 secondi poi dichiara il servizio failed - anche se Suricata sta girando correttamente. Type=simple considera il processo attivo appena parte.


La regola iptables
#

sudo iptables -I INPUT -j NFQUEUE --queue-num 0

Questa singola riga trasforma Ubuntu da server passivo a IPS inline. Tutto il traffico INPUT passa attraverso Suricata prima di essere consegnato.

INPUT è la catena iptables che gestisce i pacchetti in arrivo all'host - quelli destinati a questo sistema. iptables ha tre catene principali: INPUT (arrivo), OUTPUT (partenza dall'host), FORWARD (transito verso altri host). Aggiungendo NFQUEUE a INPUT, ogni pacchetto che arriva a Ubuntu - da Kali, dal Mac, da internet - viene fermato e consegnato a Suricata prima che il kernel lo passi all'applicazione di destinazione.

sudo iptables -L INPUT -n --line-numbers
# 1  NFQUEUE  *  0.0.0.0/0  0.0.0.0/0  NFQUEUE num 0

La regola custom: drop invece di alert
#

Le regole ET Open generano alert ma non droppano. Creo /var/lib/suricata/rules/local.rules:

drop tcp any any -> $HOME_NET any (
  msg:"IPS Aggressive Port Scan Drop";
  detection_filter:track by_src, count 20, seconds 1;
  sid:9000002; rev:1; classtype:network-scan;
)

La logica della regola:

drop              → NF_DROP (non NF_ACCEPT)
tcp               → solo TCP (non ICMP, non UDP)
any any           → qualsiasi sorgente e porta
-> $HOME_NET any  → verso la rete protetta ($HOME_NET: vedi sotto)

$HOME_NET è una variabile Suricata definita in /etc/suricata/suricata.yaml, sezione vars → address-groups. Di default include la rete locale - nel lab 192.168.64.0/24. "Rete protetta" significa il segmento che stiamo difendendo, quello dove girano i nostri servizi. Usare $HOME_NET invece di un IP fisso rende la regola portabile: funziona senza modifiche su qualsiasi deployment dove il yaml è configurato correttamente.

detection_filter  → attivazione condizionale:
  track by_src    → conta per IP sorgente (l'attaccante)
  count 20        → dopo 20 pacchetti
  seconds 1       → in un secondo

track by_src e non by_dst: voglio tracciare il comportamento dell'attaccante, non del bersaglio. Non mi interessa quale porta colpisce - mi interessa quanti SYN manda.

Una regola con detection_filter non si attiva sul primo pacchetto. Si attiva al pacchetto 21. I primi 20 passano. Questo è intenzionale: un accesso legittimo non supera quella soglia. Un nmap --min-rate 1000 la supera in meno di 20 millisecondi.

Nota: la prima versione aveva anche flags:S per matchare solo i SYN. In combinazione con detection_filter, non funzionava - Suricata contava solo i SYN puri, ma nmap manda anche ACK, RST, pacchetti ibridi. Rimosso flags:S, il blocco ha iniziato a funzionare.


Il primo blocco
#

Da Kali:

sudo nmap --min-rate 1000 -p 1-1000 192.168.64.3

Su Ubuntu, tail -f /var/log/suricata/fast.log:

[Drop] [**] [1:9000002:1] IPS Aggressive Port Scan Drop
{TCP} 192.168.64.200:62851 -> 192.168.64.3:843

[Drop] [**] [1:9000002:1] IPS Aggressive Port Scan Drop
{TCP} 192.168.64.200:62851 -> 192.168.64.3:907

Su Kali, nmap:

PORT   STATE    SERVICE
22/tcp filtered ssh
80/tcp filtered http
...
999 filtered ports
IDS → porta appare "open" o "closed"
      (il SYN arriva, il sistema risponde con SYN-ACK o RST)

IPS → porta appare "filtered"
      (il SYN non arriva mai, nmap non riceve risposta, aspetta il timeout)

filtered è la firma di un firewall o IPS: nmap non riceve né risposta né RST perché il pacchetto viene scartato nel percorso, prima che il sistema operativo lo veda.


Wazuh smette di rispondere
#

Con NFQUEUE attivo, Wazuh si disconnette. Il Wazuh agent non riesce più a raggiungere il manager Docker.

Per capire il problema, devo capire il percorso della connessione.

wazuh-agent (host)
    │ connette a 127.0.0.1:1514
docker-proxy (host, ascolta su 0.0.0.0:1514)
    │ apre nuova connessione verso container
172.18.0.2:1514 (container wazuh-manager)
    │ SYN-ACK di risposta torna a 172.18.0.1 (host)
INPUT chain su br-46d49de3057f → NFQUEUE → Suricata

docker-proxy è un processo che Docker lancia automaticamente per ogni porta esposta (ports: nel compose). Ascolta sull'host (0.0.0.0:1514) e, quando arriva una connessione, apre una nuova connessione separata verso il container. Non è un semplice NAT - sono due socket distinti. Il Wazuh agent parla con il docker-proxy, non direttamente con il container.

Il loopback (127.0.0.1) è accettato prima di NFQUEUE - quello funziona. Ma il docker-proxy poi apre una nuova connessione verso il container attraverso il bridge Docker (br-46d49de3057f). La SYN-ACK di risposta arriva all'host su quell'interfaccia, non sul loopback, e finisce nel NFQUEUE.

Suricata vede la SYN-ACK ma non ha mai visto il SYN originale (che è andato su OUTPUT, non INPUT). Senza traccia del flow, il timer scade.


Leggere /proc/net/tcp: little-endian e big-endian
#

Per diagnosticarlo, leggo direttamente la tabella TCP del kernel nel container.

Prima devo sapere come cercare: 1514 in esadecimale è 05EA.

1514 ÷ 16 = 94 resto 10  (A)
 94  ÷ 16 = 5  resto 14  (E)
  5  ÷ 16 = 0  resto 5
→ 0x05EA
docker exec single-node-wazuh.manager-1 \
  bash -c "grep -i '05EA' /proc/net/tcp /proc/net/tcp6 2>/dev/null"

Output:

/proc/net/tcp:  0: 00000000:05EA  00000000:0000  0A ...  → LISTEN
/proc/net/tcp:  8: 020012AC:05EA  010012AC:EE02  03 ...  → SYN_RECV
/proc/net/tcp: 16: 020012AC:05EA  010012AC:C69A  03 ...  → SYN_RECV

Come si legge /proc/net/tcp:

Il formato è ip_hex:port_hex. Gli IP sono in little-endian - i byte sono in ordine inverso rispetto a come li leggiamo normalmente.

020012AC → leggo i byte da destra a sinistra:
  AC = 172
  12 = 18
  00 = 0
  02 = 2
→ 172.18.0.2  (IP del container)

010012AC →
  AC = 172
  12 = 18
  00 = 0
  01 = 1
→ 172.18.0.1  (Docker gateway = host)

Le porte invece sono in big-endian (network byte order, come ci si aspetta): 05EA = 0×256 + 5×256 + 14×16 + 10 = 1514 - si legge normalmente da sinistra.

Questa differenza non è casuale. Gli IP vengono salvati così com'è in memoria su architetture x86/ARM (little-endian). Le porte seguono il network byte order perché sono usate direttamente nelle intestazioni di rete. Chi conosce Bitcoin riconosce la convenzione: anche nei raw transaction Bitcoin gli interi multi-byte sono in little-endian, mentre i campi che seguono standard di rete sono big-endian. Il kernel Linux usa la stessa logica.

Gli stati TCP:

0A (hex) = 10 (dec) = TCP_LISTEN    → in ascolto, aspetta connessioni
01 (hex) = 1  (dec) = ESTABLISHED   → connessione attiva
03 (hex) = 3  (dec) = TCP_SYN_RECV  → SYN ricevuto, SYN-ACK mandato, aspetta ACK

Il three-way handshake:

sequenceDiagram
    participant C as docker-proxy (host)
    participant S as container :1514
    participant NF as NFQUEUE (INPUT chain)

    C->>S: SYN → (OUTPUT chain, non NFQUEUE)
    S->>NF: SYN-ACK → (INPUT su br-46d49de3057f)
    Note over NF: Suricata: non conosco questo flow
    NF-->>NF: timeout / stallo
    Note over S: stato: SYN_RECV (stuck)
    Note over C: nessun ACK → connessione non si apre

Due connessioni bloccate in SYN_RECV - il container ha risposto con SYN-ACK, il docker-proxy non lo ha mai ricevuto. Il three-way handshake non si chiude.


Il fix: accettare il bridge Docker prima di NFQUEUE
#

sudo iptables -I INPUT 2 -i br-46d49de3057f -j ACCEPT

Il SYN-ACK del container ora viene accettato prima di raggiungere NFQUEUE. Il three-way handshake si completa. Wazuh si riconnette.


Il bridge che cambia nome ad ogni restart
#

br-46d49de3057f - il nome è l'UUID della rete Docker. Ogni docker compose down && up ricrea la rete con un UUID diverso. La regola iptables diventa stale, cioè obsoleta: punta a un'interfaccia che non esiste più. Il kernel la ignora silenziosamente, il bridge nuovo non viene accettato, Wazuh ricade in SYN_RECV.

Nel docker-compose.yml aggiungo una sezione networks: che fissa il nome del bridge Linux:

networks:
  default:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: wazuh-br0
    ipam:
      config:
        - subnet: 172.20.0.0/24

default è un nome speciale in Docker Compose: tutti i servizi lo usano automaticamente senza modifiche alle singole service definition.

Dopo docker compose down && up:

ip link show wazuh-br0
# 19: wazuh-br0: <BROADCAST,MULTICAST,UP,LOWER_UP>

Nome stabile. Regola iptables permanente.


La catena iptables completa
#

Dopo tutti i fix, la catena INPUT ha questa struttura:

┌─────────────────────────────────────────────────────────────────┐
│  Chain INPUT (policy DROP)│                                                                 │
1  ACCEPT   -i lo                  → loopback                  │
│              agent → docker-proxy (127.0.0.1)│                                                                 │
2  ACCEPT   -i wazuh-br0           → Docker bridge            │
│              docker-proxy → container (SYN-ACK di ritorno)│                                                                 │
3  ACCEPT   -s 192.168.64.1 :22    → SSH dal Mac              │
│              management, bypass IPS                            │
│                                                                 │
4  ACCEPT   ESTABLISHED,RELATED    → risposte a connessioni   │
│              uscenti (apt, curl, update)│                                                                 │
5  NFQUEUE  --queue-num 0          → tutto il resto           │
│              → Suricata decide                                 │
│                                                                 │
│  6+ UFW chains                                                  │
└─────────────────────────────────────────────────────────────────┘

Perché la regola 4 (ESTABLISHED,RELATED)?

Suricata in NFQUEUE vede solo INPUT - i pacchetti in arrivo. Non vede i SYN uscenti (che vanno su OUTPUT, catena diversa). Quando una risposta SYN-ACK arriva da un server esterno (dopo un apt update, un curl, qualsiasi connessione uscente), Suricata non ha traccia del flow originale.

Senza ESTABLISHED,RELATED, tutte le connessioni TCP uscenti pendono nel vuoto - Ubuntu non riesce a scaricare aggiornamenti, fare curl, niente.

ESTABLISHED,RELATED risolve a livello di conntrack del kernel: i pacchetti che appartengono a una connessione già stabilita vengono accettati prima di NFQUEUE. Conntrack (connection tracking) è il modulo del kernel Linux che tiene traccia dello stato di ogni connessione TCP/UDP - sa che quel pacchetto in arrivo è la risposta a un SYN che abbiamo mandato noi, quindi va lasciato passare. Suricata continua a vedere i pacchetti NEW - dove si trovano i port scan, i SYN flood, le connessioni malevole.

Per il lab: curl dal Mac durante un SYN flood passa ancora per NFQUEUE (regola 3 accetta solo SSH dal Mac, non HTTP). Se la regola drop si attiva, anche il curl viene bloccato. Questo è intenzionale - è il test.


La persistenza: systemd service
#

Le regole iptables non sopravvivono al reboot. iptables-persistent sembrerebbe la soluzione naturale, ma su Ubuntu rimuove UFW - entrambi gestiscono iptables e non convivono.

La soluzione: uno script idempotente + un systemd service.

# /usr/local/bin/ips-rules.sh
#!/bin/bash
IPT=/sbin/iptables

# Rimuovi regole esistenti (se il service viene riavviato, nessun duplicato)
$IPT -D INPUT -i lo -j ACCEPT 2>/dev/null || true
$IPT -D INPUT -i wazuh-br0 -j ACCEPT 2>/dev/null || true
$IPT -D INPUT -s 192.168.64.1 -p tcp --dport 22 -j ACCEPT 2>/dev/null || true
$IPT -D INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true
$IPT -D INPUT -j NFQUEUE --queue-num 0 2>/dev/null || true

# Inserisci in ordine inverso: ognuno va in posizione 1
# L'ultimo inserito diventa il primo
$IPT -I INPUT 1 -j NFQUEUE --queue-num 0
$IPT -I INPUT 1 -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPT -I INPUT 1 -s 192.168.64.1 -p tcp --dport 22 -j ACCEPT
$IPT -I INPUT 1 -i wazuh-br0 -j ACCEPT
$IPT -I INPUT 1 -i lo -j ACCEPT

L'inserimento inverso con -I INPUT 1 funziona così:

dopo insert NFQUEUE:       [NFQUEUE, UFW...]
dopo insert ESTABLISHED:   [ESTABLISHED, NFQUEUE, UFW...]
dopo insert SSH:           [SSH, ESTABLISHED, NFQUEUE, UFW...]
dopo insert wazuh-br0:     [wazuh-br0, SSH, ESTABLISHED, NFQUEUE, UFW...]
dopo insert lo:            [lo, wazuh-br0, SSH, ESTABLISHED, NFQUEUE, UFW...]
                        ordine finale corretto
# /etc/systemd/system/ips-rules.service
[Unit]
Description=Suricata IPS iptables rules
After=network.target docker.service suricata.service
Requires=suricata.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/ips-rules.sh
ExecStop=/sbin/iptables -D INPUT -j NFQUEUE --queue-num 0
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Requires=suricata.service - il service parte solo se Suricata è attivo. Non ha senso inserire NFQUEUE se nessuno legge dalla coda.

ExecStop rimuove la regola NFQUEUE quando il service viene fermato. Se Suricata si spegne e si ferma il service, il traffico torna a fluire invece di restare bloccato in coda vuota.

active (exited) è lo stato corretto per un Type=oneshot con RemainAfterExit=yes - il processo ha terminato, le regole sono in piedi, il service è considerato running.


fail-open: no - il trade-off da conoscere per Security+
#

fail-open: no   (fail-closed)
  Se Suricata si blocca:
    → NFQUEUE rimane attivo
    → la coda si riempie
    → il kernel droppa tutto
    → zero traffico, zero accesso SSH
    → massima sicurezza, zero disponibilità

fail-open: yes  (fail-open)
  Se Suricata si blocca:
    → il kernel bypassa NFQUEUE
    → tutto il traffico passa senza ispezione
    → sistema accessibile, IPS disabilitato
    → massima disponibilità, zero sicurezza
          FAIL-CLOSED              FAIL-OPEN
          (fail-open: no)          (fail-open: yes)

Sicurezza   ████████████             ░░░░░░░░░░░░
Disponibilità ░░░░░░░░░░░░          ████████████

Non esiste la risposta giusta. Esiste il compromesso che si decide in modo consapevole in base al contesto. Un IPS su un segmento critico di rete bancaria sceglie fail-closed. Un IPS su un server di produzione accessibile solo in remoto potrebbe scegliere fail-open per evitare il lockout totale.

Per Security+: questo è il trade-off CIA - Confidentiality/Integrity vs Availability. Fail-closed privilegia le prime due, fail-open privilegia la terza.


Il risultato in Wazuh
#

Con tutto in funzione:

# Da Kali
sudo nmap --min-rate 1000 -p 1-1000 192.168.64.3

# Su Ubuntu
tail -f /var/log/suricata/fast.log
[Drop] [**] [1:9000002:1] IPS Aggressive Port Scan Drop
{TCP} 192.168.64.200:62851 -> 192.168.64.3:843

[Drop] [**] [1:9000002:1] IPS Aggressive Port Scan Drop
{TCP} 192.168.64.200:62851 -> 192.168.64.3:907

Wazuh Discover - IPS drop events da Suricata in tempo reale

2.083 eventi. Tutti con action: blocked. La pipeline completa:

sequenceDiagram
    actor Kali
    participant NF as iptables NFQUEUE
    participant Sur as Suricata
    participant FL as fast.log
    participant EVE as eve.json
    participant WA as Wazuh Agent
    participant WM as Wazuh Manager
    participant WD as Dashboard

    Kali->>NF: nmap --min-rate 1000
    NF->>Sur: pacchetto in coda (trattenuto)
    Sur->>Sur: detection_filter: 20+ SYN/sec
    Sur-->>NF: NF_DROP
    Sur->>FL: [Drop] IPS Aggressive Port Scan
    Sur->>EVE: action:blocked (JSON)
    EVE->>WA: logcollector legge il file
    WA->>WM: evento inoltrato
    WM->>WD: alert rule 86601
    Note over WD: 2083 hits visibili

Wazuh Events - dettaglio alert Suricata con action blocked


IDS vs IPS: il confronto finale
#

┌──────────────┬─────────────────────────┬─────────────────────────┐
│              │       IDS               │       IPS               │
│              │   (af-packet mode)(nfqueue mode)├──────────────┼─────────────────────────┼─────────────────────────┤
│ Posizione    │ fuori dal percorso      │ inline (nel percorso)│ Traffico     │ copia                   │ originale trattenuto    │
│ Blocco       │ NO                      │ SÌ                      │
│ Se si rompe  │ traffico passa comunque │ fail-open/closed        │
│ fast.log     │ [**][Drop]│ eve.json     │ action: allowed         │ action: blocked         │
│ nmap output  │ open / closed           │ filtered                │
│ Latenza      │ zero aggiunta           │ processing di Suricata  │
└──────────────┴─────────────────────────┴─────────────────────────┘

exit 0
#

Il salto da IDS a IPS non è una questione di configurazione - è un cambio di filosofia.

In IDS, Suricata è un testimone. Vede tutto, registra tutto, non interferisce. La risposta all'attacco è umana, avviene dopo, spesso quando il danno è già fatto.

In IPS, Suricata è nel percorso. Il kernel trattiene ogni pacchetto e aspetta. La risposta è automatica e istantanea - il blocco avviene prima che il pacchetto raggiunga la porta del servizio.

Il prezzo è la complessità: l'interfaccia di rete giusta nel yaml. Il servizio systemd con il tipo corretto. La catena iptables nell'ordine giusto. Il Docker bridge accettato esplicitamente. Le connessioni stabilite esentate. La persistenza al reboot.

Ogni pezzo mancante rompe qualcosa in modo non ovvio.

Ma quando tutto è al posto giusto, [Drop] in fast.log è tra le cose più soddisfacenti che un lab possa produrre.


Comandi usati: suricata · iptables · nmap · hping3 · systemctl · docker Concetti correlati: IDS/IPS · NFQUEUE · Wazuh · iptables chains · TCP three-way handshake

Related