Fail2ban là một công cụ bảo mật mã nguồn mở giúp bảo vệ hệ thống khỏi các cuộc tấn công brute-force (thử mật khẩu liên tục). Công cụ này hoạt động bằng cách giám sát các tệp nhật ký (log files) để phát hiện các dấu hiệu của những cuộc tấn công không mong muốn. Khi phát hiện có một địa chỉ IP thực hiện các hành vi đáng ngờ, Fail2ban sẽ thực hiện các biện pháp phòng ngừa như chặn IP đó bằng tường lửa (firewall), qua đó giảm thiểu nguy cơ bị xâm nhập.
-
Giám sát tệp nhật ký: Fail2ban đọc và phân tích các tệp nhật ký từ các dịch vụ như SSH, Apache, và Postfix.
-
Xác định các IP xấu: Dựa vào các quy tắc (filters) đã được định nghĩa trước, Fail2ban xác định các địa chỉ IP có dấu hiệu xâm nhập như nhiều lần đăng nhập thất bại trong một khoảng thời gian ngắn.
-
Thực hiện lệnh chặn: Khi phát hiện IP đáng ngờ, Fail2ban sẽ thực hiện các hành động (jails) để chặn IP, thường là sử dụng iptables để chặn truy cập hoặc thực hiện các hành động tùy chỉnh khác.
-
Gỡ chặn sau thời gian quy định: Sau một khoảng thời gian cấu hình, Fail2ban sẽ tự động gỡ chặn các IP nếu không tiếp tục phát hiện hành vi đáng ngờ.
-
Hỗ trợ đa dạng dịch vụ: Có thể thiết lập để bảo vệ nhiều dịch vụ trên hệ thống như SSH, HTTP(S), FTP, email.
-
Linh hoạt trong cấu hình: Cho phép người dùng định nghĩa các quy tắc chặn, thời gian chặn, và các hành động tuỳ chỉnh khi phát hiện IP đáng ngờ.
-
Khả năng mở rộng: Dễ dàng thêm các bộ lọc (filter) mới hoặc tùy chỉnh theo nhu cầu bảo mật.
-
Fail2ban là một giải pháp đơn giản nhưng rất hiệu quả trong việc bảo vệ hệ thống khỏi các cuộc tấn công brute-force, giúp giảm thiểu nguy cơ bị xâm nhập và gia tăng an ninh cho hệ thống của bạn.
Nhìn chung, cài Fail2ban trên linux được thực hiện dễ dàng, hỗ trợ một số hệ điều hành cơ bản. Người dùng cũng có thể cài đặt trên các hệ điều hành khác thông qua việc sử dụng python script.
Mặc dù là một repo với số lượng star cũng như thời gian phát triển dài, nhưng tài liệu sử dụng cho phần mềm khá hạn chế, chỉ được host bằng Github Wiki. Vì vậy, trong quá trình sử dụng, mình sẽ dùng phần dưới đây để bổ sung và cập nhật các thiết đặt.
Cây thư mục cấu hình của Fail2ban
/etc/fail2ban
├── action.d
│ ├── abuseipdb.conf
│ ├── apf.conf
│ ├── apprise.conf
│ ├── blocklist_de.conf
│ ├── cloudflare-token.conf
│ ├── cloudflare.conf
| ...
├── fail2ban.conf
├── fail2ban.d
├── filter.d
│ ├── 3proxy.conf
│ ├── apache-auth.conf
│ ├── apache-badbots.conf
│ ├── apache-botsearch.conf
│ ├── apache-common.conf
│ ├── apache-fakegooglebot.conf
│ ├── apache-modsecurity.conf
│ ├── apache-nohome.conf
│ ├── apache-noscript.conf
│ ├── apache-overflows.conf
│ ...
├── jail.conf
├── jail.d
│ └── 00-firewalld.conf
├── paths-common.conf
└── paths-fedora.conf
Tại best practice trên wiki các tác giả đã trình bày cơ bản về các phương pháp thực hiện cơ bản để Fail2ban hoạt động tốt và ít sử dụng tài nguyên. Tóm tắt có thể đề cập
-
Fail2ban sử dụng regex để phát hiện các truy cập bất thường trong các file nhật ký, từ đó có thông tin để cấm những truy cập này. Vì vậy cần optimize regex patterns trong các folder filter.d
và action.d
.
-
Prefiltering and Regex Order: Nếu log có nhiều các thông tin khác, có thể sử dụng prefregex
để filter bớt các thông tin này để giảm số message mà Fail2ban phải xử lý. Đồng thời sắp xếp các regex theo tần suất xuất hiện, các mẫu chung nên được viết ở trên.
-
Giảm thiểu ghi các log không liên quan, các log thông tin ứng dụng nên ghi ra file khác để Fail2ban không phân tích chúng. Đặt mức ghi log thấp và phân loại chi tiết log cho mỗi ứng dụng cũng làm tăng hiệu suất của Fail2ban (ví dụ như 403 và 404 riêng cho nginx)
-
Sử dụng tính năng increment bantime theo thời gian, tăng số thời gian cấm truy cập sau nhiều lần xác thực không thành công liên tục.
/etc/fail2ban/fail2ban.local[DEFAULT]
loglevel = INFO
logtarget = /var/log/fail2ban.log
syslogsocket = auto
socket = /var/run/fail2ban/fail2ban.sock
pidfile = /var/run/fail2ban/fail2ban.pid
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 1d
dbmaxmatches = 10
/etc/fail2ban/jail.local[INCLUDES]
before = paths-fedora.conf
[DEFAULT]
bantime.increment = true
bantime.rndtime = 300
bantime.maxtime = 1d
bantime.factor = 1
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
bantime.overalljails = false
ignoreself = true
ignoreip = 127.0.0.1/8 ::1
ignorecommand = # if an ip match with ban rule, it will run this command (script), if script exit(0), fail2ban will not ban it, otherwise 1 is ban
bantime = 30m
findtime = 1m
maxretry = 5
maxmatches = %(maxretry)s
backend = auto
usedns = raw
logencoding = auto
mode = normal
filter = %(__name__)s[mode=%(mode)s]
# Destination email address used solely for the interpolations in
# jail.{conf,local,d/*} configuration files.
#destemail = root@localhost
# Sender email address used solely for some actions
#sender = root@<fq-hostname>
# E-mail action. Since 0.8.1 Fail2Ban uses sendmail MTA for the
# mailing. Change mta configuration parameter to mail if you want to
# revert to conventional 'mail'.
#mta = sendmail
protocol = tcp
chain = <known/chain>
port = 0:65535
fail2ban_agent = Fail2Ban/%(fail2ban_version)s
banaction = nftables
banaction_allports = nftables
action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
#action_xarf = %(action_)s
#xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"]
action = %(action_)s
/etc/fail2ban/action.d/nftables.conf[Definition]
type = multiport
rule_match-allports = meta l4proto \{ <protocol> \}
rule_match-multiport = $proto dport \{ $(echo '<port>' | sed s/:/-/g) \}
match = <rule_match-<type>>
rule_stat = %(match)s <addr_family> saddr @<addr_set> <blocktype>
_nft_for_proto-multiport-iter = for proto in $(echo '<protocol>' | sed 's/,/ /g'); do
_nft_for_proto-multiport-done = done
_nft_list = <nftables> -a list chain <table_family> <table> <chain>
_nft_get_handle_id = grep -oP '@<addr_set>\s+.*\s+\Khandle\s+(\d+)$'
_nft_add_set = <nftables> add set <table_family> <table> <addr_set> \{ type <addr_type>\; \}
<_nft_for_proto-<type>-iter>
<nftables> add rule <table_family> <table> <chain> %(rule_stat)s
<_nft_for_proto-<type>-done>
_nft_del_set = { %(_nft_list)s | %(_nft_get_handle_id)s; } | while read -r hdl; do
<nftables> delete rule <table_family> <table> <chain> $hdl; done
<nftables> delete set <table_family> <table> <addr_set>
_nft_shutdown_table = { <nftables> list table <table_family> <table> | grep -qP '^\s+set\s+'; } || {
<nftables> delete table <table_family> <table>
}
actionstart = <nftables> add table <table_family> <table>
<nftables> -- add chain <table_family> <table> <chain> \{ type <chain_type> hook <chain_hook> priority <chain_priority> \; \}
%(_nft_add_set)s
actionflush = { <nftables> flush set <table_family> <table> <addr_set> 2> /dev/null; } || {
%(_nft_del_set)s
%(_nft_add_set)s
}
actionstop = %(_nft_del_set)s
<_nft_shutdown_table>
actioncheck = <nftables> list chain <table_family> <table> <chain> | grep -q '@<addr_set>[ \t]'
actionban = <nftables> add element <table_family> <table> <addr_set> \{ <ip> \}
actionunban = <nftables> delete element <table_family> <table> <addr_set> \{ <ip> \}
[Init]
table = f2b-table
table_family = inet
chain = f2b-chain
chain_type = filter
chain_hook = input
chain_priority = -1
addr_type = ipv4_addr
name = default
port = http,https
protocol = tcp, udp
blocktype = drop
nftables = nft
addr_set = addr-set-<name>
addr_family = ip
/etc/fail2ban/jail.d/nginx-limit-req.conf[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
/etc/fail2ban/filter.d/nginx-limit-req.conf# This file use default of fail2ban, I do not change any thing
Một số tài liệu hướng dẫn đã có đề cập về việc cấu hình Fail2ban, trọng tâm vào việc cấu hình chi tiết các dịch vụ trong jail.d
. Ở đây nhắc lại và đề cập một số cấu hình mà mình thấy cần phải biết.
Cài đặt mức độ chi tiết của fail2ban log. Các giá trị bao gồm: CRITICAL
, ERROR
, WARNING
, NOTICE
, INFO
logtarget = /var/log/fail2ban.log
Định nghĩa log được ghi ra vào đâu, Các giá trị có thể là: STDOUT
, STDERR
, SYSLOG
, SYSOUT
, SYSTEMD-JOURNAL
, FILE
. Mặc định log được ghi vào /var/log/fail2ban.log
.
Nếu cài đặt logtarget = SYSLOG
, cài đặt trường này thành auto hoặc ghi nó ra file
socket = /var/run/fail2ban/fail2ban.sock
Nơi lưu socket file để giao tiếp daemon.
pidfile = /var/run/fail2ban/fail2ban.pid
Nơi lưu socket pid của process fail2ban
Có sử dụng fail2ban ở interface ipv6 hay không
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
Địa chỉ lưu trữ dữ liệu của fail2ban. Fail2ban sử dụng sqlite làm database, hiện chưa hỗ trợ lưu vào các database truyền thống như mysql, postgres. Có thể sửa thành None
để không lưu hoặc memory
để chỉ lưu vào memory (sẽ bị mất dữ liệu khi restart process)
Thời gian mà danh sách bị cấm trong database được xóa đi. Mặc định là 1 ngày.
Số lượng khớp được lưu trong database với mỗi ticket.
Kích thước stack được sử dụng cho các subsequently created threads
Thời gian mặc định một source matches bị cấm.
Có cho phép fail2ban xử lý các kết nối ssh hay không
before = paths-distro.conf
Include một số cấu hình của hệ điều hành như backend log, mysql log, nginx log. File này request cấu hình trong 2 file before = paths-common.conf
và after = paths-overrides.local
. Nghĩa là mặc định, một config sẽ được cấu hình trong paths-common.conf
, hoặc paths-distro.conf
và chúng sẽ bị ghi đè bởi paths-overrides.local
nếu khác nhau.
Thời gian cấm được tăng theo cấp số nhân không. Mặc định là banTime * 1, 2, 4, 8, 16, 32.
Thời gian tối đa mà hệ thống random ngẫu nhiên để cấm một nguồn. Điều này sẽ làm giảm tác dụng của các botnet tính chính xác thời gian cấm được xóa và tấn công lại.
Thời gian tối đa cấm mà nó không tăng nữa, kể cả tiếp tục bị tấn công.
Hệ số nhân của công thức tính thời gian cấm. Mặc định là 1, thời gian được tính theo công thức bantime.formula
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
Công thức tính thời gian cấm của lần tiếp theo. Mặc định theo công thức trên, là lũy thừa hệ số 2.
bantime.multipliers = 1 5 30 60 300 720 1440 2880
Công thức tính thời gian cấm thay cho bantime.formula
. Hệ số này nhân với bantime
. Ví dụ, với bantime = 60
, thời gian cấm sẽ được tính theo các lần matches: 1 min, 5 min, 30 min, 1 hour, 5 hour, 12 hour, 1 day, 2 day
bantime.overalljails = false
Nếu một IP được matches với 1 jail, fail2ban có sử dụng nó để kiểm tra trên toàn bộ các jail config khác không. Ví dụ, một IP bị cấm bởi nginx thì có được đưa vào xem xét trên ssh, mysql hay không. Mặc định là false
Bỏ qua địa chỉ local. Fail2ban cũng không cấm các host match với địa chỉ local này.
ignoreip = 127.0.0.1/8 ::1
Các dãy địa chỉ được bỏ qua, fail2ban sẽ không cấm các địa chỉ này.
ignorecommand = /path/to/command
Nếu fail2ban xác định một IP thuộc rule bị cấm. Nó sẽ chạy command /path/to/command
, nếu script trả kết quả exit 0
, fail2ban sẽ bỏ qua địa chỉ này. Ngược lại, quá trình cấm sẽ bắt đầu. Ví dụ:
#!/bin/bash
IP=$1
MY_DYNAMIC_IP=$(/usr/bin/curl -s http://my-website/dynamic-ip-list)
if [[ "$IP" =~ $MY_DYNAMIC_IP ]]; then
exit 0 # Ignore this IP
else
exit 1 # Ban this IP
fi
Số lần lỗi trước khi bị ban
Thời gian mà một host bị cấm nếu request maxretry
trong thời gian findtime
.
maxmatches = %(maxretry)s
Số lượng log matches lưu trong memory đối với mỗi IP. Mặc định bằng maxretry
. Nghĩa là memory sẽ lưu tối đa 5 matches log cho mỗi IP.
Chỉ định backend nào sẽ được sử dụng để monitor log. Các lựa chọn có thể là pyinotify
, gamin
, polling
, systemd
và auto
.
Chỉ định có sử dụng dns để cấm hay không. Khi một DNS match với rule, fail2ban sẽ thực hiện một thao tác lookup để tìm source IP của DNS đó.
yes
: nếu một hostname được bắt gặp, một request DNS lookup được thực hiện.
warn
: nếu một hostname được bắt gặp, một request DNS lookup được thực hiện, ghi xuống log là warning.
no
: nếu một hostname được bắt gặp, nó sẽ không được sử dụng để cấm, nhưng vẫn log xuống dưới dạng info.
raw
: Sử dụng địa chỉ IP trong raw log, không thực hiện DNS lookup.
Loại encode mà các file log sử dụng. Config thông số này sẽ giúp fail2ban đọc được log của các ứng dụng để process.
Có kích hoạt jail hay không. Mặc định jail sẽ không kích hoạt, người sử dụng sau khi cài đặt và cấu hình các chi tiết liên quan sẽ thay đổi thông số này để kích hoạt fail2ban.
mode = normal
filter = %(name)s[mode=%(mode)s]
Định nghĩa mode và filter. Người dùng có thể định nghĩa nhiều mode với từng ứng dụng khác nhau.
sender = root@fq-hostname
destemail = root@localhost
mta = sendmail
Địa chỉ mà fail2ban sử dụng để gửi cùng địa chỉ nhận email thông báo. MTA là engine mà fail2ban sử dụng để gửi email.
Protocal sử dụng
Chỉ định chain được sử dụng để thêm rule. Fail2ban sử dụng iptables hoặc nftables để chặn các truy cập tới server.
Danh sách port có thể cấm. Người sử dụng có thể chỉ định một range port có thể cấm, range còn lại sẽ nằm ngoài phạm vi.
fail2ban_agent = Fail2Ban/%(fail2ban_version)s
Format của user agent. Tham khảo thêm tại https://tools.ietf.org/html/rfc7231#section-5.5.3
banaction = iptables-multiport
banaction_allports = iptables-allports
Chỉ định cách thực hiện hành động cấm. Mặc định sẽ sử dụng iptables
action_ = %(banaction)s[port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
Chỉ định hành động cấm. Chỉ cấm.
action_mw = %(action_)s
%(mta)s-whois[sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
Chỉ định hành động cấm. Cấm và gửi email thông báo cấm tới destemail
action_xarf = %(action_)s
xarf-login-attack[service=%(name)s, sender="%(sender)s", logpath="%(logpath)s", port="%(port)s"]
Chỉ định hành động cấm. Cấm và gửi email thông báo cấm tới destemail
kèm các dòng log liên quan.
action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
%(mta)s-whois-lines[sender="%(sender)s", dest="%(destemail)s", logpath="%(logpath)s", chain="%(chain)s"]
Chỉ định hành động cấm trên cloudflare và gửi email tới những người nhận và các dòng log liên quan.
action_blocklist_de = blocklist_de[email="%(sender)s", service="%(name)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"]
action_abuseipdb = abuseipdb
Gửi danh sách block tới fail2ban để các lập trình viên của họ phân tích.
Chọn default action của fail2ban. Giá trị này được overwrite bởi các giá trị action_
đã đề cập bên trên.
Các action của fail2ban quy định nó làm gì khi match 1 rule. Ở đây ta sẽ xét 2 tính năng quan trọng nhất là chặn và cảnh báo, thông qua nftables và mail. Các action khác không đề cập.
Xác định match type nào để thực hiện.
Nếu config type là custom, sử dụng config này để cấu hình rule custom đó.
rule_match-allports = meta l4proto { <protocol> }
Nếu config type là custom, sử dụng config này để cấu hình rule custom đó.
rule_match-multiport = $proto dport { $(echo '<port>' | sed s/:/-/g) }
Nếu config type là multiport, sử dụng config này để cấu hình rule custom đó.
match = <rule_match-<type>>
Phân giải rule dựa trên type
rule_stat = %(match)s <addr_family> saddr @<addr_set> <blocktype>
Một rule hoàn chỉnh để fail2ban cung cấp cho nftables nhập vào các chain.
_nft_for_proto-multiport-iter = for proto in $(echo '<protocol>' | sed 's/,/ /g'); do
Chỉ định lệnh shell lặp lại cho các giao thức trong ngữ cảnh nftables. Nghĩa là nó sẽ loop theo các protocol đã được config.
#Example:
protocol = tcp, udp
# Rule will final to this
for proto in tcp udp; do
# Commands inside the loop will execute for:
# proto = "tcp" in the first iteration
# proto = "udp" in the second iteration
done
_nft_list = <nftables> -a list chain <table_family> <table> <chain>
Câu lệnh để list all rule trong một chain
_nft_get_handle_id = grep -oP '@<addr_set>\s+.*\s+\Khandle\s+(\d+)$'
Câu lệnh dùng để tìm và trích xuất handle từ chuỗi đầu vào.
Thêm một tập địa chỉ vào nftables
Xóa các rule và tập địa chỉ set trong nftables
Câu lệnh xóa table trong nftables khi shutdown fail2ban
Chỉ định chuỗi hành động khi start service
Chỉ định chuỗi hành động xóa các chỉ mục trong tập địa chỉ
Chỉ định chuỗi hành động dừng cấu hình Fail2ban
Chỉ định chuỗi hành động kiểm tra các rule trong chain
Chỉ định chuỗi hành động thêm một rule cấm vào nftables
Chỉ định chuỗi hành động xóa một rule (unban) trong chain
Các cấu hình trong phần này quy định các thiết đặt để tạo table, chain, family của nftables.