Ho preso una configurazione "semplice" dal wizard, tanto per iniziare.
Ho assegnato IP statici ad entrambe le interfacce e specificato che il sistema opererà
come firewall/gateway. L'impostazione del firewall e modifiche alla configurazione
adottata ci accompagneranno fino alla fine, dell'installazione quindi è inutile
sperare di prendere ora una configurazione che vada bene fino alla fine.
Lo script è piuttosto lungo e la presenza dei commenti tende ad essere dispersiva
ma è necessaria. In sostanza lo script inizialmente imposta le variabili che utilizzerà
per definire le regole (indirizzi IP e percorsi dei file utilizzati per inviare
i comandi al firewall), carica dei moduli del kernel specifici che potrebbero non
essere attivati di default e reimposta il firewall ad una configurazione di base,
eliminando tutte le regole eventualmente presenti, eliminando tutte le chain non
standard eventualmente presenti ed impostando la policy di default per tutte le
chain predefinite su "DROP".
In questa configurazione il nostro firewall è assolutamente "muto" ed
inaccessibile via rete. Il passo successivo potrebbe essere già autorizzare i servizi
che ci interessano ma il wizard è piuttosot avanzato e fà prima una cosa diversa:
Genera delle chain personalizzate in cui farà confluire determinate tipologie di
traffico. Questo permette diverse cose, da un miglioramento delle prestazioni generali
(perchè traffico identificato da subito come malevolo viene droppato immediatamente)
ad una approfondita analisi dei log, per individuare comportamenti anomali o tentativi
reiterati di attacco. iRedMail, il tool che utilizzerò per configurare il server
di posta installa e configura fail2ban, che è un tool di analisi dei log per il
ban automatico degli IP o degli host sospettati di essere malevoli e fà largo uso
proprio di chain personalizzate. E' quindi utile inserirne fin da subito nel
nostro script e capire come funzionino perchè dopo dovremmo rimetterci mano.
Nello specifico lo script definisce le seguenti chain personalizzate e le popola
con le regole indicate nella sezione "Populate User Chains":
codice:
# User-Specified Chains
# Create a chain to filter INVALID packets
$IPT -N bad_packets
# Create another chain to filter bad tcp packets
$IPT -N bad_tcp_packets
# Create separate chains for icmp, tcp (incoming and outgoing),
# and incoming udp packets.
$IPT -N icmp_packets
# Used for UDP packets inbound from the Internet
$IPT -N udp_inbound
# Used to block outbound UDP services from internal network
# Default to allow all
$IPT -N udp_outbound
# Used to allow inbound services if desired
# Default fail except for established sessions
$IPT -N tcp_inbound
# Used to block outbound services from internal network
# Default to allow all
$IPT -N tcp_outbound
Le regole definiscono una serie di "parametri" che per permettono di identificare
il traffico che arriva al nostro firewall, principalmente sulla base delle caratteristiche
del pacchetto stesso. Non sono in grado di disquisire sulla correttezza o meno delle
regole utilizzate, semplicemente mi limito a leggerle e modificare quelle che voglio
agiscano diversamente.
Ad esempio, mi interessa che il mio sistema risponda ai ping, almeno dalla rete
locale. Identifico quindi la sezione relativa al traffico ICMP e modifico le regole
relative (elimino parte dei commenti per questioni di spazio).
codice:
# icmp_packets chain
...
# Echo - uncomment to allow your system to be pinged.
#Uncomment the LOG command if you also want to log PING attempts
#
# $IPT -A icmp_packets -p ICMP -s 0/0 --icmp-type 8 -j LOG \
# --log-prefix "Ping detected: "
# $IPT -A icmp_packets -p ICMP -s 0/0 --icmp-type 8 -j ACCEPT
$IPT -A icmp_packets -p ICMP -i !$INET_IFACE --icmp-type 8
-j ACCEPT
# Bydefault, however, drop pings without logging. Blaster
# and other worms have infected systems blasting pings.
# Comment the line below if you want pings logged, but it
# will likely fill your logs.
#$IPT -A icmp_packets -p ICMP -s 0/0 --icmp-type 8 -j DROP
$IPT -A icmp_packets -p ICMP -i $INET_IFACE --icmp-type 8 -j
DROP
Ho commentato le righe presenti di default e le ho sostituite con comandi personalizzati:
"$IPT -A icmp_packets -p ICMP -i !$INET_IFACE --icmp-type 8 -j ACCEPT"
accetta tutto il traffico ICMP type 8 (il classico ping) che non viene dall'interfaccia
$INET_IFACE che abbiamo deciso essere quella esterna. Il "!" davanti ad
un valore ne inverte il senso quindi questo comando si legge: "Nella chain
"icmp_packets" se identifichi un pacchetto nel protocollo ICMP di tipo
8 che non proviene dall'interfaccia $INET_IFACE accettalo". Lo stesso risultato
si poteva ottenere indicando "-i $LOCAL_IFACE" al posto di "-i !$INET_IFACE"
ma il metodo che ho utilizzato è più generale e dovrebbe funzionare bene anche in
presenza di più di una interfaccia LAN.
Analogamente, dobbiamo droppare il solo traffico ICMP type 8 proveniente dalla interfaccia
$INET_IFACE ed è quello che fà la regola "$IPT -A icmp_packets -p ICMP -i $INET_IFACE
--icmp-type 8 -j DROP" definita subito dopo.
Le sezioni successive iniziano finalmente a lavorare sul filtering vero e proprio
dei servizi. Dopo aver sostanzialmente abilitato tutto il traffico in uscita, inizia
a popolare le chain relative ai pacchetti in INPUT.
codice:
# INPUT Chain #
echo "Process INPUT chain ..."
# Allow all on localhost interface
$IPT -A INPUT -p ALL -i $LO_IFACE -j ACCEPT
# Drop bad packets
$IPT -A INPUT -p ALL -j bad_packets
...
# Rules for the private network (accessing gateway system itself)
$IPT -A INPUT -p ALL -i $LOCAL_IFACE -s $LOCAL_NET -j ACCEPT
$IPT -A INPUT -p ALL -i $LOCAL_IFACE -d $LOCAL_BCAST -j ACCEPT
# Inbound Internet Packet Rules
# Accept Established Connections
$IPT -A INPUT -p ALL -i $INET_IFACE -m state --state ESTABLISHED,RELATED \
-j ACCEPT
...
La prima regola abilita tutto il traffico "INPUT" sul loopback, come avevamo
deciso in precedenza e subito dopo ci mostra il primo utilizzo delle chain personalizzate
definite prima: per droppare i bad packet non si limita a istruire il firewall per
dropparli ma li reindirizza alla chain denominata "bad_packet" e quindi
il pacchetto seguirà dal quel momento uscità dalla chain "INPUT" per seguire
le regole definite nella chain bad_packet.
Le successive regole sostanzialmente accettano tutto il traffico proveniente dall'interfaccia
sulla lan proveniente dalla range di IP della rete locale e il traffico proveniente
dalla stessa rete e destinato al braodcast della stessa rete locale.
Interessante la prossima regola: Viene accettato tutto il traffico proveniente dall'interfaccia
di rete esterna (internet) ma solo se relativo a connessioni esistenti o già stabilite.
Questo blocca tutto il traffico in ingresso che non sia stato richiesto dall'interno
della nostra rete. E' una regola fondamentale, perchè, per capirci, in assenza
di questo trust un eventuale browser sul firewall non sarebbe in grado di visualizzare
alcuna pagina web in quanto il traffico di risposta proveniente dal server esterno
verrebbe droppato.
codice:
#
# FORWARD Chain
# Drop bad packets
$IPT -A FORWARD -p ALL -j bad_packets
# Accept TCP packets we want to forward from internal sources
$IPT -A FORWARD -p tcp -i $LOCAL_IFACE -j tcp_outbound
# Accept UDP packets we want to forward from internal sources
$IPT -A FORWARD -p udp -i $LOCAL_IFACE -j udp_outbound
# If not blocked, accept any other packets from the internal interface
$IPT -A FORWARD -p ALL -i $LOCAL_IFACE -j ACCEPT
# Deal with responses from the internet
$IPT -A FORWARD -i $INET_IFACE -m state --state ESTABLISHED,RELATED \
-j ACCEPT
Questa chain invece processa il traffico relativo che transita sul nostro firewall
ma che è invece relativo ai client della nostra rete. Anche qui i bad_packet vengono
girati alla chain competente (e quindi non vengono inoltrati ai client della nostra
rete) ed i pacchetti validi vengono instradati attraverso le chain competenti ad
analizzarli. Anche qui viene accettato il traffico in ingresso se relativo a connessioni
esistenti o richieste dall'interno della rete.
codice:
# OUTPUT Chain #
echo "Process OUTPUT chain ..."
# Generally trust the firewall on output
# However, invalid icmp packets need to be dropped
# to prevent a possible exploit.
$IPT -A OUTPUT -m state -p icmp --state INVALID -j DROP
# Localhost
$IPT -A OUTPUT -p ALL -s $LO_IP -j ACCEPT $IPT -A OUTPUT -p ALL -o $LO_IFACE -j
ACCEPT
# To internal network
$IPT -A OUTPUT -p ALL -s $LOCAL_IP -j ACCEPT $IPT -A OUTPUT -p ALL -o $LOCAL_IFACE
-j ACCEPT
# To internet
$IPT -A OUTPUT -p ALL -o $INET_IFACE -j ACCEPT
# Log packets that still don't match
$IPT -A OUTPUT -m limit --limit 3/minute --limit-burst 3 -j LOG \
--log-prefix "OUTPUT packet died: "
Questa chain chiude la tabella "FILTER" e riguarda il traffico in uscita.
In questo caso stiamo autorizzando tutto il traffico valido proveniente dal nostro
sistema e diretto all'esterno. Come dice il commento alla prima regola, il traffico
non valido viene comunque droppato per evitare che possa essere veicolo di eventuali
attacchi, magari verso altri sistemi.
Il contenuto delle tabelle "NAT" e "MANGLE" è davvero scarno
ma per ora è tutto quello che ci serve, si limita ad abilitare il masquerading nella
tabella NAT e lascia vuota la tabella MANGLE.
Lo script utilizza una sintassi diversa per abilitare il masquerading rispetto a
quella che avevo indicato in precedenza. A quanto ho capito cercando informazioni,
questa sintassi è preferibile nel caso l'interfaccia esterna abbia un IP statico,
mentre quelle che avevo indicato è preferibile in presenza di un IP dinamico. Io
ho IP statico quindi la adotto ma me lo segno lo stesso:
codice:
###############################################################################
# # nat table
# ###############################################################################
echo "Load rules for nat table ..."
###############################################################################
# # PREROUTING chain
# ###############################################################################
# # POSTROUTING chain
#
# Sintassi per abilitare il masquerading da preferire in presenza di un IP
# statico sull'interfaccia esterna:
$IPT -t nat -A POSTROUTING -o $INET_IFACE \
-j SNAT --to-source $INET_ADDRESS
# Sintassi per abilitare il masquerading da preferire in presenza di un IP
# dinamico sull'interfaccia esterna:
# $IPT -t nat -A POSTROUTING -o $INET_IFACE -j MASQUERADE
###############################################################################
# # mangle table
# ###############################################################################
# The mangle table is used to alter packets. It can alter or mangle them in
# several ways. For the purposes of this generator, we only use its ability
# to alter the TTL in packets. However, it can be used to set netfilter
# mark values on specific packets. Those marks could then be used in another
# table like filter, to limit activities associated with a specific host, for
# instance. The TOS target can be used to set the Type of Service field in
# the IP header. Note that the TTL target might not be included in the
# distribution on your system. If it is not and you require it, you will
# have to add it. That may require that you build from source.
echo "Load rules for mangle table ..."
Questi due screen sono dei port-scan effettuati sulle due interfacce di rete del
server di test appena avviato (e quindi senza firewall configurato) e dopo aver
attivato il firewall ottenuto con lo script generato dal wizard:
Per ottenere i primi due screen, con l'elenco completo dei servizi disponibili
sul server, sono stati sufficienti meno di 10 secondi ciascuno. Per gli ultimi due,
ho invece fermato a mano lo scanner dopo circa 2 ore ciascuno.
Notiamo innanzitutto due cose: Nei primi due screen i servizi ssh e webmin (spostato
dalla 10000 alla 15000) sono presenti solo per la rete interna (192.168.100.150)
quindi la configurazione che avevo impostato per restringere l'accesso ssh e
webmin effettivamente funziona, negli ultimi due screen invece non ci sono porte
aperte quindi il nostro sistema, pur effettuando correttamente il forwarding ed
il routing per i client della rete, è assolutamente irraggiungibile via rete quindi
neppure via ssh o webmin per l'amministrazione.
Dobbiamo aggiungere allo script i servizi che vogliamo poter utilizzare sul nostro
server: Nello specifico io voglio che la macchina svolga i compiti di server di
posta (quindi SMTP, POP3, IMAP con SSL), server web (con e senza SSL), server DNS
e DHCP per la rete locale. Devo inoltre ricordarmi di abilitare la porta che ho
assegnato a webmin, in questo caso solo protocollo TCP.
Sfruttando il solito wizard, otteniamo un nuovo script che aggiunge le seguenti
regole alla tabella FILTER, sotto varie chain:
codice:
# DNS Server
# Configure the server to use port 53 as the source port for requests
# Note, if you run a caching-only name server that only accepts queries
# from the private network or localhost, you can comment out this line.
$IPT -A udp_inbound -p UDP -s 0/0 --destination-port 53 -j ACCEPT
# If you don't query-source the server to port 53 and you have problems,
# uncomment this rule. It specifically allows responses to queries
# initiated to another server from a high UDP port. The stateful
# connection rules should handle this situation, though.
# $IPT -A udp_inbound -p UDP -s 0/0 --source-port 53 -j ACCEPT
...
# DNS Server - Allow TCP connections (zone transfers and large requests)
# This is disabled by default. DNS Zone transfers occur via TCP.
# If you need to allow transfers over the net you need to uncomment this line.
# If you allow queries from the 'net, you also need to be aware that although
# DNS queries use UDP by default, a truncated UDP query can legally be
# submitted via TCP instead. You probably will never need it, but should
# be aware of the fact.
# $IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 53 -j ACCEPT
# Web Server
# HTTP
#$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 80 -j ACCEPT
# HTTPS (Secure Web Server)
#$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 443 -j ACCEPT
# Email Server (SMTP)
#$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 25 -j ACCEPT
# Email Server (POP3)
#$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 110 -j ACCEPT
# Email Server (IMAP4)
#$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 143 -j ACCEPT
# SSL Email Server (POP3)
#$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 995 -j ACCEPT
# SSL Email Server (IMAP4)
#$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 993 -j ACCEPT
# sshd
$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 22 -j ACCEPT
# User specified allowed TCP protocol
$IPT -A tcp_inbound -p TCP -s 0/0 --destination-port 15000 -j ACCEPT
...
# Allow DHCP client request packets inbound from internal network
$IPT -A INPUT -p UDP -i $LOCAL_IFACE --source-port 68 --destination-port 67 \
-j ACCEPT
Ho evidenziato in rosso quello che non mi convince o devo cambiare.
Il mio DNS sarà solo locale, quindi posso commentare la regola "$IPT -A udp_inbound
-p UDP -s 0/0 --destination-port 53 -j ACCEPT", come da istruzioni dell'autore
del wizard. Inoltre, se mi va bene che web e mail siano fruibili da qualunque rete
(-s 0/0), non mi va assolutamente bene che lo siano il servizio ssh e la porta su
cui è in ascolto webmin quindi cambio le due regole relative come segue:
codice:
# sshd $IPT -A tcp_inbound -p TCP -i $LOCAL_IFACE --destination-port 22 -j ACCEPT
# User specified allowed TCP protocol $IPT -A tcp_inbound -p TCP -i $LOCAL_IFACE
--destination-port 15000 -j ACCEPT
In questo modo, il servizio ssh e webmin dovrebbero essere raggiungibili solo dall'interfaccia
di rete interna.