Toto je starší verze dokumentu!
Fail2ban
Software k dynamickému blokování IP adres, když dělají něco, co nemají.
apt install fail2ban cd /etc/fail2ban cp jail.conf jail.local
Akce - složka action.d
Pro webserver jsem nastavoval, aby se zablokoval při portscanningu přístup na všechny porty, kromě 80 a 443. K tomu mi posloužil vytvoření soubor /etc/fail2ban/action
# Fail2Ban action configuration file for ufw # # You are required to run "ufw enable" before this will have any effect. # # The insert position should be appropriate to block the required traffic. # A number after an allow rule to the application won't be of much use. [Definition] actionstart = actionstop = actioncheck = actionban = [ -n "<application>" ] && app="app <application>" ufw prepend <blocktype> from <ip> $app ufw prepend allow proto tcp from <ip> to <destination> port 80,443 $app #ufw insert <insertpos> <blocktype> from <ip> to <destination> port 80 $app actionunban = [ -n "<application>" ] && app="app <application>" ufw delete <blocktype> from <ip> $app ufw delete allow proto tcp from <ip> to <destination> port 80,443 $app #ufw delete <blocktype> from <ip> to <destination> port 80 $app [Init] # Option: insertpos # Notes.: The position number in the firewall list to insert the block rule insertpos = 1 # Option: blocktype # Notes.: reject or deny blocktype = deny # Option: destination # Notes.: The destination address to block in the ufw rule destination = any # Option: application # Notes.: application from sudo ufw app list application = # DEV NOTES: # # Author: Guilhem Lettron # Enhancements: Daniel Black
Filtry - složka filter.d
Tady je konfigurace k jednotlivým službám. Každá služba zapisuje do syslogu pokusy o přihlášení v různém formátu. Filtry skrz regulární výrazy ukazují, co hledat. Tím, si lze fail2ban rozšířit i na služby, pro které defaultně určen není.
Přidáme /etc/fail2ban/filter.d/vpnserver.conf
# Fail2Ban filter for SoftEther authentication failures # Made by quixrick and jonisc # Thanks to quixrick from Reddit! https://reddit.com/u/quixrick [INCLUDES] # Read common prefixes. If any customizations available -- read them from # common.local before = common.conf #Enable multi line support. Doesn't work with versions < 0.9 [Init] maxlines = 2 # The regular expression filter follows [Definition] failregex =IP address: <HOST>.*\n.*User authentication failed.* ignoreregex=
Dále /etc/fail2ban/filter.d/portscan.conf:
# Fail2Ban filter for portscan # https://serverfault.com/questions/629709/trouble-with-fail2ban-ufw-portscan-filter # https://dodwell.us/security/ufw-fail2ban-portscan.html # # [Definition] failregex = .*\[UFW BLOCK\] IN=.* SRC=<HOST> ignoreregex =
Dále pro /etc/fail2ban/filter.d/nextcloud.conf:
[INCLUDES] before = common.conf [Definition] _groupsre = (?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*) failregex = ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Login failed: datepattern = ,?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?" ignoreregex =
Našel jsem i vlastní ssh - /etc/fail2ban/filter.d/mysshd.conf ve kterém je přidaný řádek, který banuje už i preauth při ssh.
^Connection closed by <HOST> port \d+ \[preauth\]$
Tady obsah celého souboru.
# Fail2Ban filter for openssh # # If you want to protect OpenSSH from being bruteforced by password # authentication then get public key authentication working before disabling # PasswordAuthentication in sshd_config. # # # "Connection from <HOST> port \d+" requires LogLevel VERBOSE in sshd_config # [INCLUDES] # Read common prefixes. If any customizations available -- read them from # common.local before = common.conf [DEFAULT] _daemon = sshd # optional prefix (logged from several ssh versions) like "error: ", "error: PAM: " or "fatal: " __pref = (?:(?:error|fatal): (?:PAM: )?)? # optional suffix (logged from several ssh versions) like " [preauth]" __suff = (?: \[preauth\])?\s* __on_port_opt = (?: port \d+)?(?: on \S+(?: port \d+)?)? # for all possible (also future) forms of "no matching (cipher|mac|MAC|compression method|key exchange method|host key type) found", # see ssherr.c for all possible SSH_ERR_..._ALG_MATCH errors. __alg_match = (?:(?:\w+ (?!found\b)){0,2}\w+) [Definition] prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID>%(__pref)s<F-CONTENT>.+</F-CONTENT>$ cmnfailre = ^[aA]uthentication (?:failure|error|failed) for <F-USER>.*</F-USER> from <HOST>( via \S+)?\s*%(__suff)s$ ^User not known to the underlying authentication module for <F-USER>.*</F-USER> from <HOST>\s*%(__suff)s$ ^Failed \S+ for invalid user <F-USER>(?P<cond_user>\S+)|(?:(?! from ).)*?</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$) ^Failed \b(?!publickey)\S+ for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$) ^<F-USER>ROOT</F-USER> LOGIN REFUSED.* FROM <HOST>\s*%(__suff)s$ ^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> from <HOST>%(__on_port_opt)s\s*$ ^User <F-USER>.+</F-USER> from <HOST> not allowed because not listed in AllowUsers\s*%(__suff)s$ ^User <F-USER>.+</F-USER> from <HOST> not allowed because listed in DenyUsers\s*%(__suff)s$ ^User <F-USER>.+</F-USER> from <HOST> not allowed because not in any group\s*%(__suff)s$ ^Connection closed by <HOST> port \d+ \[preauth\]$ ^refused connect from \S+ \(<HOST>\)\s*%(__suff)s$ ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*3: .*: Auth fail%(__suff)s$ ^User <F-USER>.+</F-USER> from <HOST> not allowed because a group is listed in DenyGroups\s*%(__suff)s$ ^User <F-USER>.+</F-USER> from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*%(__suff)s$ ^pam_unix\(sshd:auth\):\s+authentication failure;\s*logname=\S*\s*uid=\d*\s*euid=\d*\s*tty=\S*\s*ruser=<F-USER>\S*</F-USER>\s*rhost=<HOST>\s.*%(__suff)s$ ^(error: )?maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?%(__suff)s$ ^User <F-USER>.+</F-USER> not allowed because account is locked%(__suff)s ^<F-MLFFORGET>Disconnecting</F-MLFFORGET>: Too many authentication failures(?: for <F-USER>.+?</F-USER>)?%(__suff)s ^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>: 11: ^<F-NOFAIL>Connection <F-MLFFORGET>closed</F-MLFFORGET></F-NOFAIL> by <HOST>%(__suff)s$ ^<F-MLFFORGET><F-NOFAIL>Accepted publickey</F-NOFAIL></F-MLFFORGET> for \S+ from <HOST>(?:\s|$) mdre-normal = mdre-ddos = ^Did not receive identification string from <HOST>%(__suff)s$ ^Connection <F-MLFFORGET>reset</F-MLFFORGET> by <HOST>%(__on_port_opt)s%(__suff)s ^<F-NOFAIL>SSH: Server;Ltype:</F-NOFAIL> (?:Authname|Version|Kex);Remote: <HOST>-\d+;[A-Z]\w+: ^Read from socket failed: Connection <F-MLFFORGET>reset</F-MLFFORGET> by peer%(__suff)s mdre-extra = ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*14: No supported authentication methods available%(__suff)s$ ^Unable to negotiate with <HOST>%(__on_port_opt)s: no matching <__alg_match> found. ^Unable to negotiate a <__alg_match>%(__suff)s$ ^no matching <__alg_match> found: mdre-aggressive = %(mdre-ddos)s %(mdre-extra)s cfooterre = ^<F-NOFAIL>Connection from</F-NOFAIL> <HOST> failregex = %(cmnfailre)s <mdre-<mode>> %(cfooterre)s # Parameter "mode": normal (default), ddos, extra or aggressive (combines all) # Usage example (for jail.local): # [sshd] # mode = extra # # or another jail (rewrite filter parameters of jail): # [sshd-aggressive] # filter = sshd[mode=aggressive] # mode = normal #filter = sshd[mode=aggressive] ignoreregex = maxlines = 1 journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd datepattern = {^LN-BEG} # DEV Notes: # # "Failed \S+ for .*? from <HOST>..." failregex uses non-greedy catch-all because # it is coming before use of <HOST> which is not hard-anchored at the end as well, # and later catch-all's could contain user-provided input, which need to be greedily # matched away first. # # Author: Cyril Jaquier, Yaroslav Halchenko, Petr Voralek, Daniel Black and Sergey Brester aka sebres # Rewritten using prefregex (and introduced "mode" parameter) by Serg G. Brester.
/etc/fail2ban/filter.d/ufwban.conf
[INCLUDES] before = common.conf [Definition] #failregex = .*\[UFW BLOCK\] IN=.* SRC=<HOST> failregex = UFW BLOCK.* SRC=<HOST> ignoreregex =
Testování regulárního výrazu vlastního filtru
Tady je ukázka, jak lze otestovat vlastní filtr.
fail2ban-regex /var/log/ufw.log '.*\[UFW BLOCK\] IN=.* SRC=<HOST>'
Editace fail2ban.conf
Tady popisuji hodnoty, které jde oproti defaultnímu nastavení změnil.
dbpurgeage = 10d
Editace jail.local
Nejprve je třeba v souboru jail.local nastavit banaction i na ipv6 a bannování skrz ipset. V provozu může vzniknout docela dost ip adres, které je třeba bannovat, tak bude rozumné kvůli rychlosti firewallu použít ipset místo defaultního nastavení:
banaction = iptables-ipset-proto6 banaction_allports = iptables-ipset-proto6-allports
Zkoušel jsem zvolit banaction = ufw, ale tady se zabanovala celá ip adresa a já chci blokovat pouze konkrétní službu. Zejména co se ssh týče.
Služby, na kterých má fail2ban vyset se musí zapnout pomocí enabled = true. U SSH jsem volil mód aggressive. Blokace je 10 minut pro všechny služby, pokud zkusí 3 a více pokusů za posledních 10 minut.
# "bantime" is the number of seconds that a host is banned. bantime = 10m # A host is banned if it has generated "maxretry" during the last "findtime" # seconds. findtime = 10m # "maxretry" is the number of failures before a host get banned. maxretry = 3
Tady se odehrává zbytek nastavení fail2ban. Sepisuji zejména změny, které jsem provedl. U SSH jsem si změnil port a to je potřeba promítnout i v konfiguraci. Je tam vidět port 3333.
ignoreip = 127.0.0.1/8 ::1 [sshd] # To use more aggressive sshd modes set filter parameter "mode" in jail.local: # normal (default), ddos, extra or aggressive (combines all). # See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details. mode = aggressive port = ssh,3333 logpath = %(sshd_log)s backend = %(sshd_backend)s enabled = true
Dále jsem přidal sshd-slow, který zabanuje ssh pro případ, že se za 1 den někdo zkusí celkem 10x přihlásit. V tomto případě zablokuje na 1week. To už je docela problematický prohřešek - to už někdo opravdu dost zkouší.
[sshd-slow] filter = sshd[mode=aggressive] maxretry = 6 findtime = 1d bantime = 1w logpath = %(sshd_log)s backend = %(sshd_backend)s enabled = true [vpnserver] enabled = true port = 443,500,4500 protocol = udp logpath = /usr/vpnserver/security_log/DEFAULT/sec.log banaction = iptables-allports # Uncomment the following line if you want to be notified about banned IP's action= %(action_mwl)s filter=vpnserver [vpnserver-securenat] enabled = true logpath = /usr/vpnserver/security_log/securenat/sec.log port = all protocol = all banaction = iptables-allports # Uncomment the following line if you want to be notified about banned IP's action= %(action_mwl)s filter=vpnserver
Pozapínal jsem další smysluplné věci, jako apache, fakegoogle boty atp.
Zapnul jsem i recidive. Pokud někdo bude mít ban 3x za 12 hodin, bude celá IP adresa zablokovaná na 1 týden. Tady budu muset být opatrný a kontrolovat, protože hrozí, že někdo přijde z veřejné IP adresy, 3x dostane BAN a zablokuje celou veřejnou IP adresu.
[recidive] enabled = true logpath = /var/log/fail2ban.log banaction = %(banaction_allports)s bantime = 1w findtime = 12h
Poslední, co jsem aplikoval je ochrana na portscan. Tady je to opět potenciálně nebezpečná záležitost u webserveru. V praxi to prochází log, hledá jednoduchý řetězec, zda byla ip adresa zablokovaná a pokud byla zablokovaná 10x, tak proběhne ban skrz ufw.
Problémy jsou tam tyto:
- Nečíslovaný seznamNečíslovaný seznamufw mám na loglevel = low, takže když někdo oskenuje všechny porty, tak se neprovedou tisíce záznamů do logu, ale jenom pár. Zvyšovat hodnotu zase nemá smysl kvůli zpomalení sítě a serveru.
- pokud mi tam bude padat moc IP adres, tak ufw nebude dobrá volba, kvůli velkému počtu IP adres. Může pak dojít ke zpomalení. Tady je řešení vytvořit kopii současných skriptů action.log/iptables-ipset-proto6 a upravit je, aby zablokovaly pouze port 4444 a povolily 80 a 443. Nechci zablokovat všechny služby pro danou IP adresu, protože mi někdo oskenoval porty.
[ufw-port-scan] enabled = true filter = portscan logpath = /var/log/ufw.log banaction = ufw-portscan bantime = 1w maxretry = 300 findtime = 3h
Vyřazení IP adresy z banlistu
fail2ban-client set vpnserver unbanip 89.24.33.115
fail2ban-client unban 89.24.33.115
Vypsání všech zabanovaných adres
fail2ban-client banned