Set IP rivisitati
C'è già una risposta che menziona i set IP. Tuttavia, è piuttosto monodimensionale in quanto si concentra sui miglioramenti delle prestazioni rispetto alle regole classiche e sul fatto che i set IP mitigano il problema che si ha con un sacco di indirizzi IP individuali che non possono essere facilmente espressi come sottorete nella notazione CIDR.
Notazione usata di seguito
Perché ipset
userò la notazione letta ipset restore
e scritta da ipset save
.
In corrispondenza di iptables
(e ip6tables
) regole userò la notazione come letta iptables-restore
e scritta da iptables-save
. Questo rende una notazione più breve e mi permette di evidenziare le potenziali regole solo IPv4 (prefisso -4
) o solo IPv6 (prefisso -6
).
In alcuni esempi dirotteremo il flusso di pacchetti in un'altra catena. Si presume che la catena esista in quel punto, quindi le linee per creare le catene non vengono prodotte (né viene menzionato il nome della tabella o i comandi COMMIT
alla fine).
Set IP avanzati
I set di IP possono fare molto di più di quanto menzionato nell'altra risposta e dovresti assolutamente leggere la documentazione del set di IP ( ipset(8)
) insieme iptables-extensions(8)
a oltre a questa breve voce qui.
Per esempio io concentrerà principalmente su tre tipi di set: hash:ip
, hash:net
e list:set
, ma ci sono più di quelli e tutti hanno i casi d'uso valide.
Ad esempio, puoi anche abbinare i numeri di porta, non solo gli indirizzi IP .
Salvataggio e ripristino di set IP come con iptables-save
eiptables-restore
È possibile creare dichiarazioni di set IP in blocco e importarle eseguendo il piping in ipset restore
. Se si desidera rendere il comando più resiliente rispetto a voci già esistenti, utilizzare ipset -exist restore
.
Se le tue regole sono in un file chiamato, default.set
dovresti usare:
ipset -exist restore < default.set
Un file del genere può contenere voci per create
insiemi e add
voci in esse. Ma generalmente la maggior parte dei comandi dalla riga di comando sembra avere una versione corrispondente nei file. Esempio (creazione di un set di server DNS):
create dns4 hash:ip family inet
create dns6 hash:ip family inet6
# Google DNS servers
add dns4 8.8.8.8
add dns4 8.8.4.4
add dns6 2001:4860:4860::8888
add dns6 2001:4860:4860::8844
Qui viene creato un set per IPv4 ( dns4
) e uno per IPv6 ( dns6
).
Timeout su set IP
I timeout nei set IP possono essere impostati come predefiniti per set e anche per voce. Ciò è molto utile per gli scenari in cui si desidera bloccare temporaneamente qualcuno (ad es. Per la scansione delle porte o il tentativo di forzare il proprio server SSH).
Il modo in cui funziona è il seguente (impostazione predefinita durante la creazione di set IP):
create ssh_loggedon4 hash:ip family inet timeout 5400
create ssh_loggedon6 hash:ip family inet6 timeout 5400
create ssh_dynblock4 hash:ip family inet timeout 1800
create ssh_dynblock6 hash:ip family inet6 timeout 1800
Torneremo su questi set particolari di seguito e sulla logica del perché sono impostati così come sono.
Se si desidera impostare il timeout per un determinato indirizzo IP, si potrebbe semplicemente dire:
add ssh_dynblock4 1.2.3.4 timeout 7200
Per bloccare IP 1.2.3.4 per due ore anziché la mezz'ora (impostata) predefinita.
Se dovessi guardarlo ipset save ssh_dynblock4
dopo poco tempo, vedresti qualcosa sulla falsariga di:
create ssh_dynblock4 hash:ip family inet hashsize 1024 maxelem 65536 timeout 1800
add ssh_dynblock4 1.2.3.4 timeout 6954
Avvertenze di timeout
- i timeout sono una caratteristica di ogni set. Se il set non è stato creato con il supporto del timeout riceverai un errore (ad es
Kernel error received: Unknown error -1
.).
- i timeout sono indicati in secondi. Utilizzare espressioni aritmetiche di Bash per ottenere da minuti a secondi, ad esempio. Per esempio:
sudo ipset add ssh_dynblock4 1.2.3.4 timeout $((120*60))
Verifica dell'esistenza di una voce in un determinato set IP
All'interno dei tuoi script può essere utile vedere se esiste già una voce. Ciò può essere ottenuto con ipset test
cui restituisce zero se la voce esiste e diversa da zero in caso contrario. Quindi i soliti controlli possono essere applicati in uno script:
if ipset test dns4 8.8.8.8; then
echo "Google DNS is in the set"
fi
Tuttavia, in molti casi preferirai utilizzare l' -exist
opzione a ipset
per dirigerlo a non lamentarti delle voci esistenti.
Popolamento degli insiemi IP dalle iptables
regole
Questa, secondo me, è una delle caratteristiche killer dei set IP. Non solo puoi abbinare le voci di un set IP, ma puoi anche aggiungere nuove voci a un set IP esistente.
Ad esempio in questa risposta a questa domanda hai:
-A INPUT -p tcp -i eth0 -m state --state NEW --dport 22 -m recent --update --seconds 15 -j DROP
-A INPUT -p tcp -i eth0 -m state --state NEW --dport 22 -m recent --set -j ACCEPT
... con l'intenzione di limitare i tentativi di connessione a SSH (porta TCP 22). Il modulo utilizzato recent
tiene traccia dei recenti tentativi di connessione. Invece del state
modulo, preferisco il conntrack
modulo, comunque.
# Say on your input chain of the filter table you have
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
# Then inside the SSH chain you can
# 1. create an entry in the recent list on new connections
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
# 2. check whether 3 connection attempts were made within 2 minutes
# and if so add or update an entry in the ssh_dynblock4 IP set
-4 -A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock4 src --exist
-6 -A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock6 src --exist
# 3. last but not least reject the packets if the source IP is in our
# IP set
-4 -A SSH -m set --match-set ssh_dynblock4 src -j REJECT
-6 -A SSH -m set --match-set ssh_dynblock6 src -j REJECT
In questo caso sto reindirizzando il flusso verso la SSH
catena in modo che non debba ripetermi con -p tcp --dport ssh
ogni singola regola.
Reiterare:
-m set
rende iptables
consapevole che stiamo utilizzando switch dal set
modulo (che gestisce i set IP)
--match-set ssh_dynblock4 src
dice di iptables
far corrispondere l' indirizzo source ( src
) con il set ( ssh_dynblock4
)
- questo corrisponde a
sudo ipset test ssh_dynblock4 $IP
(dove $IP
contiene l'indirizzo IP di origine per il pacchetto)
-j SET --add-set ssh_dynblock4 src --exist
aggiunge o aggiorna l' indirizzo source ( src
) dal pacchetto nel set IP ssh_dynblock4
. Se esiste una voce ( --exist
) verrà semplicemente aggiornata.
- questo corrisponde a
sudo ipset -exist add ssh_dynblock4 $IP
(dove $IP
contiene l'indirizzo IP di origine per il pacchetto)
Se invece desideri corrispondere all'indirizzo di destinazione / destinazione, utilizzeresti dst
invece di src
. Consultare il manuale per ulteriori opzioni.
Insiemi di insiemi
I set IP possono contenere altri set. Ora, se hai seguito l'articolo fino a qui, ti starai chiedendo se è possibile combinare set. E ovviamente lo è. Per i set IP dall'alto possiamo creare due set di giunti ssh_dynblock
e ssh_loggedon
rispettivamente per contenere solo i set solo IPv4 e solo IPv6:
create ssh_loggedon4 hash:ip family inet timeout 5400
create ssh_loggedon6 hash:ip family inet6 timeout 5400
create ssh_dynblock4 hash:ip family inet timeout 1800
create ssh_dynblock6 hash:ip family inet6 timeout 1800
# Sets of sets
create ssh_loggedon list:set
create ssh_dynblock list:set
# Populate the sets of sets
add ssh_loggedon ssh_loggedon4
add ssh_loggedon ssh_loggedon6
add ssh_dynblock ssh_dynblock4
add ssh_dynblock ssh_dynblock6
E la domanda successiva che dovrebbe sorgere nella tua mente è se questo ci consente di abbinare e manipolare i set IP in modo indipendente dalla versione IP.
E la risposta a ciò è clamorosa: SÌ! (ahimè, questo non è stato documentato esplicitamente l'ultima volta che ho controllato)
Di conseguenza, le regole della sezione precedente possono essere riscritte per leggere:
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
-A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock src --exist
-A SSH -m set --match-set ssh_dynblock src -j REJECT
che è molto più conciso. E sì, questo è provato e testato e funziona come un fascino.
Mettendo tutto insieme: SSH difesa dalla forza bruta
Sui miei server ho uno script eseguito come cron
lavoro che prende un sacco di nomi host e li risolve in indirizzi IP, quindi inserendolo nel set IP per "host fidati". L'idea è che gli host fidati facciano più tentativi di accedere al server e non sono necessariamente bloccati per tutto il tempo.
Al contrario, ho interi paesi bloccati dalla connessione al mio server SSH, con la (potenziale) eccezione di host fidati (cioè l'ordine delle regole è importante).
Tuttavia, questo è lasciato come esercizio per il lettore. Qui vorrei aggiungere una soluzione ordinata che utilizzerà i set contenuti nel ssh_loggedon
set per consentire il passaggio dei successivi tentativi di connessione e non essere soggetti allo stesso controllo degli altri pacchetti.
È importante ricordare i timeout predefiniti di 90 minuti ssh_loggedon
e 30 minuti ssh_dynblock
quando si osservano le seguenti iptables
regole:
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
-A SSH -m set --match-set ssh_loggedon src -j ACCEPT
-A SSH -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
-A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock src --exist
-A SSH -m set --match-set ssh_dynblock src -j REJECT
Ormai dovresti chiederti come finisce l'indirizzo IP di connessione nei ssh_loggedon
sottoinsiemi. Quindi continua a leggere ...
Bonus: aggiunta dell'IP con cui accedi durante l'accesso SSH
Se hai sperimentato sshrc
e amici, avrai appreso delle sue carenze. Ma PAM viene in soccorso. Un modulo chiamato pam_exec.so
ci consente di invocare uno script durante l'accesso SSH in un punto in cui sappiamo che l'utente è ammesso.
In /etc/pam.d/sshd
sotto le voci pam_env
e pam_selinux
aggiungere la seguente riga:
session optional pam_exec.so stdout /path/to/your/script
e assicurati che la tua versione dello script ( /path/to/your/script
sopra) esista e sia eseguibile.
PAM utilizza le variabili di ambiente per comunicare cosa sta succedendo, quindi puoi usare un semplice script come questo:
#!/bin/bash
# When called via pam_exec.so ...
SETNAME=ssh_loggedon
if [[ "$PAM_TYPE" == "open_session" ]] && [[ -n "$PAM_RHOST" ]]; then
[[ "x$PAM_RHOST" != "x${PAM_RHOST//:/}" ]] && SETNAME="${SETNAME}6" || SETNAME="${SETNAME}4"
ipset -exist add $SETNAME "$PAM_RHOST"
fi
Sfortunatamente l' ipset
utilità non sembra avere gli smart built-in di netfilter. Quindi dobbiamo distinguere tra IPv4 e IPv6 IP set quando aggiungiamo la nostra voce. Altrimenti ipset
supponiamo che vogliamo aggiungere un altro set al set di set, anziché IP. E ovviamente è improbabile che ci sia un set che prende il nome da un IP :)
Quindi controlliamo :
l'indirizzo IP e 6
in questo caso aggiungiamo il nome impostato 4
.
La fine.