使用fail2ban保护jumpserver不被暴力破解

说明

  • 操作系统CentOS-7
  • jumpserver版本2.0.1
  • 根据jumpserver官方文档里的安全建议,做了以下加固措施
    • 操作系统保持最新的软件版本
    • 仅开放4432222端口
    • 禁止公网PING响应
  • 可是使用一段时间之后,在网页端管理后台,查看日志审计->登录日志,发现有很多password_failed的日志。
  • 登录jumpserver服务器仔细查看koko的日志后,发现基本都是来自同一个IP地址对多个常见用户名进行暴力登录破解。
  • 看来jumpserver名气挺大的,默认2222端口被暴力登录破解还是挺频繁的,建议修改成其他端口。
  • jumpserver默认的安全设置只能限制同一用户的登录失败次数禁止登录时间间隔,这就有点不能忍了。
  • 于是乎祭出fail2ban配置自定义过滤器来收拾收拾。
  • 这里不仅限于JumpServer,其他应用也可以参考此方法一通百通

安装fail2ban

  • 添加EPEL源
1
yum install -y epel-release
  • 安装fail2ban
1
yum install -y fail2ban

jms-koko日志解析

  • koko的日志文件存放在/opt/koko/data/logs/koko.log

  • 这里放一段koko日志的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
YYYY-MM-DD 20:22:24 [INFO] SSH conn[a521be6ef6f8849382c3e4e3a6ae37b3836ebda0] Failed password for games from 172.105.86.202
YYYY-MM-DD 20:23:09 [INFO] SSH conn[1a7a38b23a7763014829ce550b3d9c8e6f6da59f] authenticating user user1 password
YYYY-MM-DD 20:23:09 [ERRO] User user1 Authenticate err: POST http://127.0.0.1:8080/api/v1/authentication/tokens/ failed, get code: 400, {"error":"password_failed","msg":"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 4 次(账号将被临时 锁定 15 分钟)"}
YYYY-MM-DD 20:23:09 [INFO] SSH conn[1a7a38b23a7763014829ce550b3d9c8e6f6da59f] Failed password for user1 from 172.105.86.202
YYYY-MM-DD 20:23:29 [INFO] SSH conn[a97528c3a63c613831cf6f3702bc5cefdddbe798] authenticating user pythia password
YYYY-MM-DD 20:23:30 [ERRO] User pythia Authenticate err: POST http://127.0.0.1:8080/api/v1/authentication/tokens/ failed, get code: 400, {"error":"password_failed","msg":"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 4 次(账号将被临时 锁定 15 分钟)"}
YYYY-MM-DD 20:23:30 [INFO] SSH conn[a97528c3a63c613831cf6f3702bc5cefdddbe798] Failed password for pythia from 211.99.229.3
YYYY-MM-DD 20:23:54 [INFO] SSH conn[30a806d6168f01f798524727a1be285a8a280b77] authenticating user pc password
YYYY-MM-DD 20:23:54 [ERRO] User pc Authenticate err: POST http://127.0.0.1:8080/api/v1/authentication/tokens/ failed, get code: 400, {"error":"password_failed","msg":"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 4 次(账号将被临时 锁定 15 分钟)"}
YYYY-MM-DD 20:23:54 [INFO] SSH conn[30a806d6168f01f798524727a1be285a8a280b77] Failed password for pc from 172.105.86.202
YYYY-MM-DD 20:24:34 [INFO] SSH conn[f8e65cd6ecce476a902b4a2d60596d59bd94558a] authenticating user pollinate password
YYYY-MM-DD 20:24:34 [ERRO] User pollinate Authenticate err: POST http://127.0.0.1:8080/api/v1/authentication/tokens/ failed, get code: 400, {"error":"password_failed","msg":"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 4 次(账号将被临时 锁定 15 分钟)"}
YYYY-MM-DD 20:24:34 [INFO] SSH conn[f8e65cd6ecce476a902b4a2d60596d59bd94558a] Failed password for pollinate from 211.99.229.3
YYYY-MM-DD 20:24:40 [INFO] SSH conn[89b6e5d651dbca4ec13f311f84efbc96d7bdc140] authenticating user cuf password
YYYY-MM-DD 20:24:40 [ERRO] User cuf Authenticate err: POST http://127.0.0.1:8080/api/v1/authentication/tokens/ failed, get code: 400, {"error":"password_failed","msg":"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 4 次(账号将被临时 锁定 15 分钟)"}
YYYY-MM-DD 20:24:40 [INFO] SSH conn[89b6e5d651dbca4ec13f311f84efbc96d7bdc140] Failed password for cuf from 172.105.86.202
YYYY-MM-DD 20:25:25 [INFO] SSH conn[ab15f086f2bea09db3109e1355460384c618f569] authenticating user test password
YYYY-MM-DD 20:25:25 [ERRO] User test Authenticate err: POST http://127.0.0.1:8080/api/v1/authentication/tokens/ failed, get code: 400, {"error":"password_failed","msg":"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 4 次(账号将被临时 锁定 15 分钟)"}
YYYY-MM-DD 20:25:25 [INFO] SSH conn[ab15f086f2bea09db3109e1355460384c618f569] Failed password for test from 172.105.86.202
  • 可以看到登录失败的日志格式是统一的以Failed password for <用户名> from <IP地址>结尾
  • 那么可以开始准备配置fail2ban的自定义过滤器了

fail2ban自定义过滤器

  • fail2ban自带了很多常见服务的过滤器,但是jumpserver不在其中,于是乎自己弄一个吧

  • fai2ban支持基于正则表达式的过滤,因此可以先用正则表达式匹配一下登录失败的日志

1
fail2ban-regex /opt/koko/data/logs/koko.log 'Failed password for [A-Za-z0-9]+ from <HOST>'
  • 命令运行之后会显示结果
    • Failregex: 8234 total代表有8234条记录被匹配
    • Date template hits指该日志中有满足格式的日期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Running tests
=============

Use failregex line : Failed password for [A-Za-z0-9]+ from <HOST>
Use log file : /opt/koko/data/logs/koko.log
Use encoding : UTF-8


Results
=======

Failregex: 8234 total
|- #) [# of hits] regular expression
| 1) [8234] Failed password for [A-Za-z0-9]+ from <HOST>
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
| [27451] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T| ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)?
`-

Lines: 27451 lines, 0 ignored, 8234 matched, 19217 missed
[processed in 1.62 sec]

Missed line(s): too many to print. Use --print-all-missed to print all 19217 lines
  • 既然正则匹配已经ok了,那么就可以配置自定义过滤器了
1
vi /etc/fail2ban/filter.d/jms-koko.conf
  • 添加如下内容
1
2
3
[Definition]
failregex = Failed password for [A-Za-z0-9]+ from <HOST>
ignoreregex =

配置fail2ban服务

添加fail2ban配置

1
vim /etc/fail2ban/jail.local
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[DEFAULT]
# 默认禁止IP地址15天,单位是秒:
bantime = 1296000
# ban的动作使用iptables-multiport
banaction = iptables-multiport
# 忽略IP,注意加上自己的IP,不然被误封就麻烦了
ignoreip = 127.0.0.1/8 192.168.0.0/24
[jms-koko]
# 直接所有协议drop包,覆盖上面的banaction
action = iptables-allports[protocol=all,blocktype=DROP]
enabled = true
# filter指定刚才配置的自定义过滤器
filter = jms-koko
# koko端口默认是2222
port = 2222
# koko的日志路径,请修改成自己的路径地址
logpath = /opt/koko/data/logs/koko.log
# 最大重试次数
maxretry = 5
# 禁止12小时,这里会覆盖上面default定义的bantime
bantime = 43200

检查配置

1
fail2ban-client -t
  • 输出示例
1
OK: configuration test is successful
  • 可以通过加-v或者-d输出详细日志
1
fail2ban-client -t -v
1
fail2ban-client -t -d

启动fail2ban

1
systemctl enable --now fail2ban.service

查看fail2ban的状态

1
fail2ban-client status jms-koko
  • 根据输出结果可以看到172.105.86.202已经被ban了
1
2
3
4
5
6
7
8
9
Status for the jail: jms-koko
|- Filter
| |- Currently failed: 1
| |- Total failed: 24
| `- File list: /opt/koko/data/logs/koko.log
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 172.105.86.202

查看fail2ban日志

  • /var/log/fail2ban.log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2020-08-30 20:25:33,924 fail2ban.server         [1346]: INFO    --------------------------------------------------
2020-08-30 20:25:33,924 fail2ban.server [1346]: INFO Starting Fail2ban v0.11.1
2020-08-30 20:25:33,924 fail2ban.observer [1346]: INFO Observer start...
2020-08-30 20:25:33,931 fail2ban.database [1346]: INFO Connected to fail2ban persistent database '/var/lib/fail2ban/fail2ban.sqlite3'
2020-08-30 20:25:33,933 fail2ban.database [1346]: WARNING New database created. Version '4'
2020-08-30 20:25:33,975 fail2ban.filter [1346]: INFO maxRetry: 5
2020-08-30 20:25:33,975 fail2ban.filter [1346]: INFO encoding: UTF-8
2020-08-30 20:25:33,976 fail2ban.filter [1346]: INFO findtime: 600
2020-08-30 20:25:33,976 fail2ban.actions [1346]: INFO banTime: 1296000
2020-08-30 20:25:33,976 fail2ban.jail [1346]: INFO Creating new jail 'jms-koko'
2020-08-30 20:25:33,977 fail2ban.jail [1346]: INFO Jail 'jms-koko' uses poller {}
2020-08-30 20:25:33,978 fail2ban.jail [1346]: INFO Initiated 'polling' backend
2020-08-30 20:25:33,979 fail2ban.filter [1346]: INFO maxRetry: 5
2020-08-30 20:25:33,980 fail2ban.filter [1346]: INFO encoding: UTF-8
2020-08-30 20:25:33,980 fail2ban.filter [1346]: INFO findtime: 600
2020-08-30 20:25:33,980 fail2ban.actions [1346]: INFO banTime: 43200
2020-08-30 20:25:33,980 fail2ban.filter [1346]: INFO Added logfile: '/opt/koko/data/logs/koko.log' (pos = 0, hash = f52047cfc39a7880f2301858f7172d30)
2020-08-30 20:25:33,987 fail2ban.jail [1346]: INFO Jail 'jms-koko' started
2020-08-30 22:32:23,861 fail2ban.filter [1346]: INFO [jms-koko] Found 172.105.86.202 - 2020-08-30 22:32:23
2020-08-30 22:33:17,912 fail2ban.filter [1346]: INFO [jms-koko] Found 172.105.86.202 - 2020-08-30 22:33:17
2020-08-30 22:34:05,961 fail2ban.filter [1346]: INFO [jms-koko] Found 172.105.86.202 - 2020-08-30 22:34:05
2020-08-30 22:34:58,023 fail2ban.filter [1346]: INFO [jms-koko] Found 172.105.86.202 - 2020-08-30 22:34:57
2020-08-30 22:35:49,282 fail2ban.filter [1346]: INFO [jms-koko] Found 172.105.86.202 - 2020-08-30 22:35:49
2020-08-30 22:35:49,374 fail2ban.actions [1346]: NOTICE [jms-koko] Ban 172.105.86.202
....

查看防火墙规则

1
iptables -t filter -L -n -v
  • 输出如下
    • 被ban的ip会提示端口不可达
1
2
3
4
5
6
7
8
9
10
Chain INPUT (policy ACCEPT 779K packets, 71M bytes)
pkts bytes target prot opt in out source destination
8516 498K f2b-jms-koko tcp -- * * 0.0.0.0/0 0.0.0.0/0 multiport dports 2222

<此处忽略很多行>

Chain f2b-jms-koko (1 references)
pkts bytes target prot opt in out source destination
30 1800 REJECT all -- * * 172.105.86.202 0.0.0.0/0 reject-with icmp-port-unreachable
8461 495K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0

事前

  • 根据截图可以看到同一个IP疯狂尝试登录

事前

事后

  • 一晚上功夫就新增了十几个被ban的ip
1
2
3
4
5
6
7
8
9
Status for the jail: jms-koko
|- Filter
| |- Currently failed: 1
| |- Total failed: 116
| `- File list: /opt/koko/data/logs/koko.log
`- Actions
|- Currently banned: 16
|- Total banned: 20
`- Banned IP list: 52.130.66.202 202.107.251.28 222.186.10.49 149.202.3.113 59.42.36.243 139.129.230.201 92.246.16.39 47.110.225.78 219.84.203.57 165.227.5.140 37.139.9.23 39.104.163.108 190.144.100.58 94.228.182.244 89.228.59.72 91.205.217.22
  • 单个IP最多试5次就拜拜了

事后

解封操作

  • 说不准有时候会有倒霉蛋输错密码导致IP被ban,可以通过用fail2ban-client命令解封IP地址
  • JumpServer账号锁定的话要在JumpServer里面解锁账号
1
fail2ban-client set jms-koko unbanip IP地址

docker环境配置

说明

  • docker会创建两个chain,DOCKER和DOCKER-USE,并且会由这两个chain优先处理数据包
  • 其中DOCKER由docker进程维护,DOCKER-USER由用户自己管理
  • 默认情况下,通往容器的数据包不会经过fail2ban的chain处理
  • 需要修改actionstart和actionstop,将fail2ban的chain插入到DOCKER-USER这个chain里面
  • 这样数据包处理路径为 DOCKER-USER ===> f2b-jms-koko ===> DOCKER

添加action

1
vim /etc/fail2ban/actions.d/iptables-allports-with-docker.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[INCLUDES]
before = iptables-common.conf
[Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
<iptables> -t filter -I DOCKER-USER -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<iptables> -t filter -D DOCKER-USER -p <protocol> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
<iptables> -t filter -n -L DOCKER-USER | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
[Init]

修改jail.local

  • action = iptables-allports 改为 iptables-allports-with-docker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[DEFAULT]
# 默认禁止IP地址15天,单位是秒:
bantime = 1296000
# ban的动作使用iptables-multiport
banaction = iptables-multiport
# 忽略IP,注意加上自己的IP,不然被误封就麻烦了
ignoreip = 127.0.0.1/8 192.168.0.0/24
[jms-koko]
# 直接所有协议drop包,覆盖上面的banaction
action = iptables-allports-with-docker[protocol=all,blocktype=DROP]
enabled = true
# filter指定刚才配置的自定义过滤器
filter = jms-koko
# koko端口默认是2222
port = 2222
# koko的日志路径,请修改成自己的路径地址
logpath = /opt/koko/data/logs/koko.log
# 最大重试次数
maxretry = 5
# 禁止12小时,这里会覆盖上面default定义的bantime
bantime = 43200

重启fail2ban服务

1
systemctl restart fail2ban.service

查看防火墙规则

  • f2b-jms-koko链
1
2
3
4
5
6
7
8
9
10
11
12
iptables -t filter -n -v -L f2b-jms-koko

Chain f2b-jms-koko (2 references)
pkts bytes target prot opt in out source destination
0 0 DROP all -- * * 46.249.32.138 0.0.0.0/0
0 0 DROP all -- * * 39.98.132.227 0.0.0.0/0
0 0 DROP all -- * * 39.108.215.9 0.0.0.0/0
0 0 DROP all -- * * 39.106.119.140 0.0.0.0/0
0 0 DROP all -- * * 39.104.178.110 0.0.0.0/0
0 0 DROP all -- * * 222.186.57.205 0.0.0.0/0
0 0 DROP all -- * * 199.195.254.185 0.0.0.0/0
313 26414 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
  • DOCKER-USER链
1
2
3
4
5
6
iptables -t filter -n -v -L DOCKER-USER

Chain DOCKER-USER (1 references)
pkts bytes target prot opt in out source destination
5166 672K f2b-jms-koko all -- * * 0.0.0.0/0 0.0.0.0/0
38M 31G RETURN all -- * * 0.0.0.0/0 0.0.0.0/0

至此大功告成