Prerequisiti
Una macchina collegata ad un network (anche internet), oppure che abbia
più di un'interfaccia di rete.
Una minima conoscenza della rete e del tcp/ip è molto utile per
capire quello che si sta facendo.
Essere in grado di ricompilare il kernel; anche se vengono descritte
tutte le opzioni da inserire per il corretto funzionamento del
firewall, si assume che si sia in grado di abilitare tutte le opzioni
necessarie a far funzionare il proprio hardware e tutto il resto.
Cosa è un firewall?
Il firewall è una struttura che si occupa di analizzare il traffico
entrante e uscente da una macchina e decidere in base alle regole
definite come trattare i vari pacchetti.
Questo tipo di funzionalità è detto packet filtering.
A chi serve un firewall?
A chiunque abbia una connessione ad internet, in particolare se è una
connessione permanente. Quando siamo connessi ad una rete siamo
potenziali vittime di attacchi, e in molti casi, se la nostra macchina
venisse compromessa e facesse dei danni ad altre macchina, potremmo
essere considerati responsabili dell'accaduto.
È quindi importante una sensibilizzazione sui pericoli delle reti.
Un firewall serve anche e soprattutto nei punti periferici della rete,
accoppiato ad un router per definire le regole di passaggio da una
LAN a internet (o ad un'altra intranet).
Configurazione base dei servizi per una macchina in rete
Prima di mettere una macchina Linux su di una qualsiasi rete bisogna
decidere esattamente quali servizi offrire e disabilitare di
conseguenza tutti quelli superflui, in modo da diminuire i possibili buchi
del sistema.
Ora occupiamoci dei servizi che invece vogliamo lasciare attivi, ma
solo per un uso locale, quindi non dovremmo avere porte tcp in ascolto
sull'interfaccia esterna (quella a contatto con internet), ne' porte
udp.
Controlliamo quindi la situazione con "netstat -tunap" che oltretutto ci
dice anche quale processo tiene aperta quella data porta e su che
interfaccia; se il Local Address è 0.0.0.0 significa che quella porta è
in ascolto su tutte le interfacce.
Anche se un firewall ben configurato non permetterebbe comunque, a livello
di packet filtering, di raggiungere quelle porte, è sempre meglio creare
una situazione di ridondanza. Come? Facciamo un piccolo esempio:
root@damun:~# netstat -tunap
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1303/sshd
[...]
In questo caso sshd è in ascolto sulla porta 22 di tutte le
interfacce; come fare a imporre a ssh di stare in ascolto solo su una
data interfaccia, ad esempio quella di loopback? Semplicissimo, nel
file di configurazione di ssh c'è un'opzione ListenAddress che di
default è 0.0.0.0, andiamo a cambiare questa opzione in ListenAddress
127.0.0.1, riavviamo ssh, e quindi
root@damun:~# netstat -tunap | grep 22
tcp 0 0 127.0.0.1:22 0.0.0.0:* LISTEN 1310/sshd
Un'altra cosa che ci può aiutare a creare robustezza e a migliorare
la sicurezza del nostro sistema è l'utilizzo (se possibile) del
tcpwrapper tcpd e dei file per il controllo degli accessi /etc/hosts.allow
e /etc/hosts.deny.
Consiglio come al solito di negare ogni accesso ad ogni servizio
(basta inserire la stringa "ALL : ALL" in hosts.deny) e di abilitare
esplicitamente in hosts.allow i servizi che devono essere permessi.
Questo consiglio (negare ogni cosa e abilitare solo quello che serve)
è un MUST quando si parla di sicurezza, infatti quando andremo a
costruire il firewall seguiremo la stessa strategia.
Purtoppo non tutti i servizi possono essere controllati da tcpd
(quelli che non partono da inetd/xinetd e non sono linkati alle
libwrap) e non tutti possono essere associati ad un'interfaccia
specifica ed è qui che interviene il firewall.
Implementazione dello strato di packet filtering in Linux
Andremo ora ad introdurre le funzionalità di firewalling disponibili con
il kernel di Linux.
Nei kernel 2.0 la sezione che si occupa di packet filtering è
chiamata ipfwadm, che è stata sostituita nei kernel 2.2 da ipchains e
ora nei kernel 2.4 da iptables.
La maggior parte di questi software è inclusa direttamente nel kernel
di linux, quindi essi sono molto più veloci di qualsiasi software che
gira in user-mode.
Il passaggio da ipchains a iptables è stato segnato da grandissime
novità tra le quali spicca la stateful inspection grazie al modulo di
connection tracking (approfondiremo questo concetto in seguito).
In questa sede andremo ad analizzare prevalentemente iptables, viste
le importanti novità introdotte, anche se gran parte dei concetti
saranno validi anche per ipchains.
Predisposizione del sistema per l'utilizzo del firewall (iptables)
Per avere l'ultima versione di netfilter è probabilmente necessario
"patchare" il kernel, quindi consiglio caldamente di recuperare un
kernel cosiddetto "vanilla" (cioè proveniente da kernel.org o da uno
dei suoi mirrors ma non i sorgenti che vengono allegati alla vostra
distribuzione preferita che probabilmente saranno già abbondantemente
modificati) e di seguire questa semplice procedura.
Scompattiamo i sorgenti dell'ultima versione di iptables e spostiamoci
nella directory appena creata:
tar xjf iptables-1.2.7a.tar.bz2
cd iptables-1.2.7a
Ora procediamo alla vera e propria patch dei sorgenti del kernel:
make pending-patches KERNEL_DIR=/path/to/your.kernel.source
dove /path/to/your.kernel.source è la directory che contiene i
sorgenti del kernel, solitamente /usr/src/linux.
Qui partirà il programma "Patch-o-matic" che analizzerà i sorgenti del
kernel, trovando le possibili patch da applicare e vi chiederà, per
ogni patch, se volete applicarla o no (in generale è consigliato
applicare ogni patch a meno di evidenti controindicazioni). Se tutto
va a buon fine, alla fine dovreste leggere qualcosa come:
Excellent! Kernel is now ready for compilation.
Una volta completata questa semplice operazione è necessario
ricompilare lo stesso kernel abilitando le seguenti opzioni:
[ Code maturity level options ]
* Prompt for development and/or incomplete code/drivers (CONFIG_EXPERIMENTAL)
YES
[ Loadable module support ]
* Enable loadable module support (CONFIG_MODULES)
YES
* Kernel module loader (CONFIG_KMOD)
YES
[ Networking options ]
* Packet socket (CONFIG_PACKET)
YES (anche se ai fini del firewall non e` indispensabile,
permette di poter usare tcpdump per debuggare)
* Packet socket: mmapped IO (CONFIG_PACKET_MMAP)
YES (velocizza il Packet protocol)
* Network packet filtering (replaces ipchains) (CONFIG_NETFILTER)
YES (questa e` l'opzione che permettera` di configurare la
sottosezione tcp-ip del kernel con netfilter, abilitando
il sottomenu IP: Netfilter Configuration )
* Unix domain sockets (CONFIG_UNIX)
YES
* TCP/IP networking (CONFIG_INET)
YES
[ Networking options --> IP: Netfilter Configuration ]
In questa sezione vi consiglio di abilitare modularmente tutte le
opzioni trovate (sono molte, probabilmente, ma se il kmod e`
adeguatamente configurato non dovrete occuparvi di caricare
manualmente i moduli di cui avete bisogno, ci pensera` il kernel
quando ne avra` bisogno).
[ Network device support ]
* Network device support (CONFIG_NETDEVICES)
YES
[ File systems ]
* /proc filesystem support (CONFIG_PROC_FS)
YES, e` richiesto per configurare dinamicamente i meccanismi di
forwarding e di nat
Le opzioni elencate qui sopra sono solo alcune di quelle che
riguardano netfilter, ovviamente sarà necessario selezionare tutte le
altre opzioni che ci servono per il corretto funzionamento della
macchina.
A questo punto dobbiamo essere sicuri che esista il programma
iptables, visto che abbiamo i sorgenti dell'ultima versione, ci
conviene ricompilarlo anche se potrebbe già essere presente nel
sistema.
Sempre dalla directory contenente i sorgenti di iptables:
make KERNEL_DIR=/path/to/your.kernel.source
make install KERNEL_DIR=/path/to/your.kernel.source
Bene, a questo punto dobbiamo costruire solo le regole che andranno a
costituire il nostro firewall.
Iptables, concetti generali
Un filtro di pacchetti (come già detto) controlla gli header dei
pacchetti e in base alle regole definite ne stabilisce la sorte.
Può decidere di:
- scartare il pacchetto
-
accettare il pacchetto, che quindi sarà passato ai livelli superiori
(tcpwrappers o direttamente l'applicazione destinata a gestire quel
pacchetto)
-
compiere altre operazioni più complesse, alcune delle quali saranno
analizzate in seguito
Il comando iptables viene usato per compiere ogni tipo di operazioni sulle
tabelle delle regole del kernel; ci sono tre tabelle indipendenti:
-
filter, la tabella di default che si occupa di filtrare o far passare i
pacchetti
-
nat, che si occupa di tradurre gli indirizzi
-
mangle, per alterazioni speciali dei pacchetti
Ogni tabella contiene un certo numero di "chain", alcune sono built-in
nella tabella e non possono essere cancellate, altre possono essere
definite dall'utente. Ogni chain è costituita da un insieme
numerato di regole che possono identificare un certo sottoinsieme di
pacchetti.
Quando arriva un pacchetto, dunque, vengono analizzate in ordine le
regole della chain che gli compete (vedere più sotto la spiegazione
di $CHAIN) e appena viene trovata una regola che corrisponde a quel
pacchetto, quella regola deciderà la sorte [TARGET] del pacchetto
stesso.
La forma di default di una regola di iptables è fatta in questo modo:
iptables -t $TABLE $ACTION $CHAIN -m $modulo -p $proto -i $interface_in \
-o $interface_out -s $source -d $destination -j $TARGET
Dove:
-
$TABLE è la tabella alla quale ci si sta riferendo; In questo
articolo tratteremo solo la tabella filter (questa è la tabella presa in
considerazione se l'opzione -t non è specificata).
-
$ACTION è l'azione da compiere sulla chain, ad esempio -A (aggiungi
una nuova regola alla chain), -P (imposta una policy di default), -F
(cancella tutte le regole di una chain), -N (crea una nuova chain), -X
(cancella una chain definita dall'utente).
-
$CHAIN è il nome di una chain; per ogni tabella esistono delle chain
built-in e delle chain che possono essere create dall'utente a partire
dalle chain di base; le catene già costruite della tabella filter
sono: INPUT (per i pacchetti destinati alla macchina stessa), OUTPUT
(per i pacchetti generati localmente) e FORWARD (per i pacchetti che
devono essere instradati attraverso due interfacce della macchina).
-
$modulo è un modulo speciale da caricare per analizzare la regola in
oggetto; utilizzeremo soprattutto il modulo "state" che ci permette di
tracciare le connessioni in base al loro stato grazie al modulo del
kernel ip_conntrack.
-
$proto è il protocollo (tcp, udp, icmp, ...).
-
$interface_in è l'interfaccia dalla quale sta entrando il pacchetto
(chain INPUT e FORWARD); $interface_out è l'interfaccia dalla quale
sta uscendo il pacchetto (chain OUTPUT e FORWARD).
-
$source e $destination sono indirizzi o classi di indirizzi per
identificare rispettivamente l'indirizzo sorgente e quello destinazione
del pacchetto.
-
$TARGET può essere un target conclusivo, cioè un target che pone
fine all'attraversamento della chain e ne decide la sorte, o una
chain definita dall'utente; i target conclusivi della tabella filter
sono essenzialmente tre: ACCEPT (fa passare il pacchetto), DROP (butta
il pacchetto) e REJECT (butta il pacchetto mandando un pacchetto icmp
tipo network-unreachable all'indirizzo sorgente di quel pacchetto).
Ci sono fondamentalmente due modi per andare a scrivere le regole:
-
possiamo inserire tutti i comandi all'interno di uno script (come
farò poi io in seguito)
-
possiamo scrivere le nostre regole direttamente dalla linea di comando
e quindi saranno subito attive.
In quest'ultimo caso per salvare la configurazione delle nostre
regole in un dato momento basta lanciare il comando
iptables-save > MiaConfig
Dove MiaConfig sarà il nome del file con la vostra configurazione,
mentre per richiamarla in un altro momento basta lanciare il comando:
iptables-restore < MiaConfig
Per vedere tutte le regole impostate all'interno delle varie chain si
usa il comando "iptables -L" (List).
Una spiegazione particolare è d'obbligo per i concetti di stateful
inspection e connection tracking perché è grazie a questi che la
configurazione di iptables risulta decisamente più semplice ed
efficace rispetto a ipchains.
Il tracciamento delle connessioni (di cui è responsabile il modulo
del kernel ip_conntrack) mantiene in memoria delle tabelle dei
pacchetti entranti e uscenti dalla macchina in modo da poter avere un
controllo non solo in base alle caratteristiche del pacchetto
analizzato, ma anche e soprattutto in base alla sua relazione con i
pacchetti precedenti, cioè allo "stato" della connessione.
L'opzione di iptables che ci permette di utilizzare questa
importantissima feature è l'estensione "state"; caricando questo
modulo possiamo specificare la sotto-opzione --state $STATO dove
$STATO è una lista di stati separati da virgole.
Sono definiti 4 tipi diversi di stato:
-
NEW per un pacchetto che crea una nuova connessione (cioè un
pacchetto tcp col flag SYN impostato oppure pacchetti udp o icmp
non dovuti a connessioni già validate)
-
ESTABLISHED per un pacchetto che fa parte di una connessione già
stabilita, cioè che ha già avuto dei pacchetti in risposta.
-
RELATED per un pacchetto relativo a connessioni esistenti ma che non
fa parte di una connessione esistente (come ad esempio un pacchetto
icmp di errore o una connessione ftp-data [porta 20] relativa ad una
connessione ftp)
-
INVALID per pacchetti che per alcune ragioni non possono essere
classificati in altro modo.
Costruzione di un firewall di base
Siamo nella situazione di avere una macchina connessa saltuariamente
oppure permanentemente a internet e caricheremo il firewall all'
attivarsi dell'interfaccia ppp o all'avvio della macchina
rispettivamente.
L'esempio proposto è molto semplice, rimando ad un eventuale articolo
futuro un esempio più realistico.
#!/bin/sh
### definizione dell'interfaccia pubblica, cambiare se necessario
EXT-IF="ppp0"
### verifica della correttezza dei moduli all'interno del kernel
depmod -ae
### caricamento dei moduli necessari
/sbin/insmod ip_conntrack
/sbin/insmod ip_conntrack_ftp
/sbin/insmod ip_conntrack_irc
### Utile per interfacce dial-up con indirizzo IP variabile.
echo "1" > /proc/sys/net/ipv4/ip_dynaddr
### azzeramento delle regole delle chain e definizione delle policy di base
iptables -F
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
### creazione di una chain user-defined
iptables -N chain-spoof
### [*] accetto su tutte le interfacce le connessioni stateful
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
### accetto tutti i pacchetti inviati sull'interfaccia di loopback
iptables -A INPUT -i lo -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
### accetto gli icmp di tipo 3 (destination unreachable) che mi servono
### per evitere lunghi timeout in caso di risposte d'errore
iptables -A INPUT -p icmp --icmp-type 3 -j ACCEPT
### redirigo alla chain-spoof tutti i pacchetti che arrivano dall'
### interfaccia esterna con indirizzi sorgente privati
iptables -A INPUT -i $EXT-IF -s 224.0.0.0/3 -j chain-spoof
iptables -A INPUT -i $EXT-IF -s 192.168.0.0/16 -j chain-spoof
iptables -A INPUT -i $EXT-IF -s 172.16.0.0/12 -j chain-spoof
iptables -A INPUT -i $EXT-IF -s 10.0.0.0/8 -j chain-spoof
iptables -A INPUT -i $EXT-IF -s 127.0.0.0/8 -j chain-spoof
iptables -A INPUT -i $EXT-IF -s 169.254.0.0/16 -j chain-spoof
### log dei pacchetti spoofati (la spiegazione del modulo limit e del
### target LOG saranno fatte prossimamente)
iptables -A chain-spoof -m limit --limit 1/s -j LOG --log-prefix \\
"Pacchetti forse spoofati" --log-level warn
### log di tutti i tentativi di connessione tcp (anche qui rimandiamo
### ls spiegazione dell'opzione tcp --syn)
iptables -A input -p tcp --syn -i $EXT-IF -j LOG --log-prefix \\
"Tentativo di connessione su $EXT-IF" --log-level warn
In questo primo esempio non si è toccata per ora la chain OUTPUT in quanto si
presume che se questa macchina è un home-computer usato solo da persone
"fidate" non debbano essere fatte limitazioni sul traffico in uscita, in futuro
vedremo anche come limitare il traffico uscente.
Invece la policy di default di INPUT è stata impostata a DROP,
cosicché se un pacchetto non dovesse ricadere in nessuna regola nella
nostra chain verrà automaticamente eliminato.
La regola fondamentale che ci permette in modo così semplice di
definire i pacchetti che possono entrare è la
[*], come si nota è
l'unica regola con target ACCEPT della chain di INPUT (a parte i
pacchetti sull'interfaccia di loopback).
Questa regola accetterà tutti e soli i pacchetti di connessioni già
stabilite e relative, quindi, essendo la policy di default a DROP, con
questa regola scartiamo automaticamente tutti i tentativi di
connessione tcp e tutti i pacchetti udp e icmp non appartenenti a
connessioni già validate e comunque iniziate da un pacchetto in
uscita inviato dalla nostra macchina.
Conclusione
Per ora siamo riusciti a proteggere adeguatamente la nostra macchina
dall'esterno, pur non avendo in nessun modo limitato le nostre
possibilità sulla rete.
Nei prossimi articoli vedremo di aggiungere al nostro schema una LAN
da proteggere (e da cui proteggersi) quindi ci occuperemo di NAT e dei
principi di routing che nella situazione descritta qui non erano
necessari.
Si cercherà anche di analizzare target e opzioni particolari che non
abbiamo potuto trattare in questo articolo ed altri argomenti inerenti:
-
Aggiunta di una rete locale da proteggere
-
Condivisione della connessione e routing
-
NAT: DNAT e redirection e SNAT e MASQUERADING
-
Controllo del traffico in uscita (magari con proxy trasparente http in aiuto)
-
Porte da bloccare per file sharing, chat...
-
Estensioni tcp, udp e icmp; target speciali (LOG,...); moduli particolari.