Skip to content

Fail2ban - Công cụ chống DDOS và brute force hiệu quả

Giới thiệu về Fail2ban

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.

Cách thức hoạt động của Fail2ban

  • 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ờ.

Một số tính năng chính của Fail2ban

  • 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.

Cấu hình Fail2ban theo best practice

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 script1.

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.

Best Practice

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.daction.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.

Mẫu cấu hình fail2ban

/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

Danh sách cấu hình Fail2ban

Một số tài liệu hướng dẫn2 đã 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ấu hình fail2ban.conf

loglevel = INFO

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.

syslogsocket = auto

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

allowipv6 = auto

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)

dbpurgeage = 1d

Thời gian mà danh sách bị cấm trong database được xóa đi. Mặc định là 1 ngày.

dbmaxmatches = 10

Số lượng khớp được lưu trong database với mỗi ticket.

stacksize = 0

Kích thước stack được sử dụng cho các subsequently created threads

Cấu hình jail.conf

bantime = 1h

Thời gian mặc định một source matches bị cấm.

enabled = true

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.confafter = 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.

bantime.increment = true

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.

bantime.rndtime = 300

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.

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.

bantime.factor = 1

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

ignoreself = true

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

maxretry = 5

Số lần lỗi trước khi bị ban

findtime = 10m

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.

backend = auto

Chỉ định backend nào sẽ được sử dụng để monitor log. Các lựa chọn có thể là pyinotify, gamin, polling, systemdauto.

usedns = warn

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.

logencoding = auto

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.

enabled = false

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.

protocol = tcp

Protocal sử dụng

chain = known/chain

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.

port = 0:65535

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.

action = %(action_)s

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ấu hình action.d

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.

type = multiport

Xác định match type nào để thực hiện.

rule_match-custom =

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.

_nft_add_set =

Thêm một tập địa chỉ vào nftables

_nft_del_set =

Xóa các rule và tập địa chỉ set trong nftables

_nft_shutdown_table =

Câu lệnh xóa table trong nftables khi shutdown fail2ban

actionstart =

Chỉ định chuỗi hành động khi start service

actionflush =

Chỉ định chuỗi hành động xóa các chỉ mục trong tập địa chỉ

actionstop =

Chỉ định chuỗi hành động dừng cấu hình Fail2ban

actioncheck =

Chỉ định chuỗi hành động kiểm tra các rule trong chain

actionban =

Chỉ định chuỗi hành động thêm một rule cấm vào nftables

actionunban =

Chỉ định chuỗi hành động xóa một rule (unban) trong chain

[Init]

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.


  1. Fail2Ban. How to install fail2ban packages. 2023. URL: https://github.com/fail2ban/fail2ban/wiki/How-to-install-fail2ban-packages (visited on 2024-11-01). 

  2. Webdock. How to configure fail2ban for common services. 2023. URL: https://webdock.io/en/docs/how-guides/security-guides/how-configure-fail2ban-common-services (visited on 2024-11-01).