介绍
SSH是连接到云服务器的事实标准方法。它耐用且可扩展 —— 随着新的加密标准的开发,可以使用它们生成新的SSH密钥,确保核心协议仍然安全。然而,没有协议或软件堆栈是完全防护的,SSH在互联网上被广泛部署意味着它代表了一个非常可预测的攻击面或攻击向量,通过它人可以尝试获得访问权限。攻击面或攻击向量。
以这种方式暴露在网络上的任何服务都是潜在的目标。如果您审查任何广泛使用的服务器上运行的SSH服务的日志,您通常会看到重复的系统化登录尝试,代表用户和机器人一样的暴力攻击。尽管您可以对SSH服务进行一些优化,以减少这些攻击成功的可能性接近于零,例如禁用密码身份验证,使用SSH密钥,它们仍然可能构成轻微但持续的风险。
对于那些完全不能接受这种责任的大规模生产部署,通常会在他们的SSH服务前面实现一个VPN,比如WireGuard,这样就不可能直接从外部互联网连接到默认的SSH端口22,而需要额外的软件抽象或网关。这些VPN解决方案被广泛信任,但会增加复杂性,并可能破坏一些自动化或其他小型软件钩子。
在承诺采用完整VPN设置之前或之后,您可以实施一个称为Fail2ban的工具。Fail2ban可以通过创建规则,自动更改您的防火墙配置,在一定数量的登录尝试失败后禁止特定IP,从而显著减轻暴力破解攻击。这将使您的服务器能够在没有您干预的情况下,加强自身对这些访问尝试的防御。
在另一个教程中,我们讨论了如何使用Fail2ban保护SSH。在本指南中,我们将更深入地讨论Fail2ban的工作原理以及您如何利用这些知识来修改或扩展该服务的行为。
Fail2ban的基础知识
Fail2ban的目的是监视常见服务的日志,以发现认证失败的模式。
当fail2ban被配置为监视服务的日志时,它会查看针对该服务配置的过滤器。该过滤器旨在通过复杂的正则表达式识别该特定服务的身份验证失败。正则表达式是一种常用的模式匹配模板语言。它将这些正则表达式模式定义为内部变量,称为failregex
。
默认情况下,Fail2ban包含常见服务的过滤器文件。当来自任何服务的日志与其过滤器中的failregex
匹配时,将针对该服务执行预定义的操作。action
是一个变量,可以根据管理员的偏好配置为执行许多不同的操作。
默认操作是通过修改本地防火墙规则来禁止违规主机/IP地址。您可以扩展此操作,例如向系统管理员发送电子邮件。
默认情况下,当检测到10分钟内发生了三次身份验证失败时,将采取行动,并且默认封禁时间为10分钟。这是可配置的。
当使用默认的iptables
防火墙时,fail2ban
在服务启动时创建一组新的防火墙规则,也称为链。它向INPUT链添加一个新规则,将所有针对端口22的TCP流量发送到新链。在新链中,它插入一个返回到INPUT链的单个规则。如果停止Fail2ban服务,则删除该链及其相关规则。
探索Fail2ban服务设置
Fail2ban通过位于/etc/fail2ban/
目录结构下的多个文件进行配置。
fail2ban.conf
文件配置一些操作设置,如守护进程记录信息的方式,以及它将使用的套接字和pid文件。然而,主要配置是在定义每个应用“jails”的文件中指定的。
默认情况下,fail2ban随附有一个jail.conf
文件。但是,这可能会在更新中被覆盖,因此您应该将此文件复制到jail.local
文件中并在那里进行调整。
如果您已经有一个jail.local
文件,请使用nano
或您喜欢的文本编辑器打开它:
如果您还没有jail.local
文件,或者您打开的文件是空白的,请复制jail.conf
文件,然后打开新文件:
我们将查看此处的可用选项,并查看此文件与系统上的其他配置文件的交互方式。
默认部分
文件的第一部分将定义 fail2ban 策略的默认值。这些选项可以在每个单独服务的配置部分中进行覆盖。
去除注释后,默认部分的全部内容如下:
[DEFAULT]
ignoreip = 127.0.0.1/8
bantime = 10m
findtime = 10m
maxretry = 3
backend = auto
usedns = warn
destemail = root@localhost
sendername = Fail2Ban
banaction = iptables-multiport
mta = sendmail
protocol = tcp
chain = INPUT
action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s", sendername="%(sendername)s"]
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
%(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s", sendername="%(sendername)s"]
action = %(action_)s
让我们来看一下这些内容的含义:
- ignoreip: 此参数标识应该被封禁系统忽略的 IP 地址。默认情况下,仅设置为忽略来自本机的流量,以防止填满自己的日志或锁定自己。
- bantime: 此参数设置封禁的时长,以秒为单位。默认值为 10 分钟。
- findtime: 此参数设置 Fail2ban 在寻找重复的失败身份验证尝试时要关注的时间窗口。默认设置为 10 分钟,这意味着软件将统计过去 10 分钟内的失败尝试次数。
- maxretry: 这设置了在
findtime
窗口内容许的失败尝试次数达到封禁之前的次数。 - backend: 此条目指定了 Fail2ban 将如何监视日志文件。
auto
设置意味着 fail2ban 将尝试pyinotify
,然后是gamin
,最后是基于可用内容的轮询算法。inotify
是 Linux 内核的内置功能,用于跟踪文件访问情况,而pyinotify
则是 Fail2ban 使用的与inotify
交互的 Python 接口。 - usedns:此选项定义是否使用反向DNS来帮助实施封禁。将其设置为“no”将会封禁IP地址本身而不是它们的域名主机名。
warn
设置将尝试查找主机名并进行封禁,但会记录活动以供审核。 - destemail:如果配置了警报邮件,将发送通知邮件的地址。
- sendername:此名称将用于生成的通知邮件的发件人字段。
- banaction:设置当达到阈值时将采取的行动。实际上这是一个路径,位于
/etc/fail2ban/action.d/
中的名为iptables-multiport.conf
的文件。它处理实际的iptables
防火墙操作以封禁IP地址。我们稍后会详细看一下这个。 - mta:将用于发送通知邮件的邮件传输代理。
- protocol:当实施IP封禁时将被丢弃的流量类型。这也是发送到新的iptables链的流量类型。
- chain:将配置为具有跳转规则以将流量发送到fail2ban漏斗的链。
其余的参数定义了可以指定的不同操作。它们通过在文本字符串中使用变量替换来传递我们上面定义的一些参数,例如:
%(var_name)s
上面的行将被替换为var_name
的内容。使用这个,我们可以知道action
变量默认设置为action_
定义(仅禁止,无邮件提醒)。
这反过来是通过调用iptables-multiport
动作来配置的,该动作需要一系列参数(服务名称、端口、协议和链)来执行禁止操作。 __name__
被替换为下面章节标题中指定的服务名称。
服务特定章节
在默认部分下面,有一些用于特定服务的章节,这些章节可用于覆盖默认设置。这遵循了只修改与正常值不同的参数的约定(约定优于配置)。
每个章节标题被指定如下:
[service_name]
任何具有enabled = true
行的部分将被读取并启用。
在每个章节中,配置了参数,包括应该用于解析日志的过滤文件(不包括文件扩展名)以及日志文件本身的位置。
记住这一点,指定SSH服务操作的章节看起来像这样:
[SSH]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 6
这使得此部分生效,并将端口设置为默认的“ssh”端口(端口22)。它告诉Fail2ban查看位于/var/log/auth.log
的日志,并使用/etc/fail2ban/filters.d
目录中名为sshd.conf
的文件中定义的过滤机制解析日志。
它需要的所有其他信息都来自于[DEFAULT]
部分中定义的参数。例如,操作将设置为action_
,它将使用iptables-multiport
banaction来封禁违规的IP地址,该操作引用/etc/fail2ban/action.d
中名为iptables-multiport.conf
的文件。
如您所见,[DEFAULT]
部分中的操作应该是通用且灵活的。使用参数替换以及提供合理默认值的参数将使得在必要时能够覆盖定义。
检查过滤器文件
为了理解配置中发生了什么,我们需要了解过滤器和动作文件,这些文件完成了大部分工作。
过滤文件将确定 fail2ban 将在日志文件中查找的行,以识别违规特征。操作文件实现了启动服务时建立防火墙结构、添加和删除规则以及停止服务时拆除防火墙结构所需的所有操作。
让我们来看看上面配置中我们 SSH 服务调用的过滤文件:
[INCLUDES]
before = common.conf
[Definition]
_daemon = sshd
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error) for .* from <HOST>( via \S+)?\s*$
^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$
^%(__prefix_line)sFailed \S+ for .*? from <HOST>(?: port \d*)?(?: ssh\d*)?(: (ruser .*|(\S+ ID \S+ \(serial \d+\) CA )?\S+ %(__md5hex)s(, client user ".*", client host ".*")?))?\s*$
^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$
^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group\s*$
^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because a group is listed in DenyGroups\s*$
^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$
ignoreregex =
[INCLUDES]
部分标题指定了在此文件之前或之后读取的其他过滤文件。在我们的示例中,读取并放置在此文件中的其他行之前的 common.conf 文件。这设置了我们将在配置中使用的一些参数。
接下来,我们有一个 [Definition]
部分,定义了过滤匹配的实际规则。首先,我们使用 _daemon
参数设置了我们正在监视的守护程序的名称。
之后,我们进行实际的 failregex
定义,它设置了在日志文件中找到匹配行时将触发的模式。这些是根据用户身份验证不正确时可能抛出的不同错误和失败来匹配的正则表达式。
行的一部分,如 %(__prefix_line)s
将替换为我们引用的 common.conf 文件中设置的参数的值。这用于匹配操作系统在使用标准方法写入日志文件时写入的不同前导信息。例如,来自 /var/log/auth.log 的某些行可能如下所示:
May 6 18:18:52 localhost sshd[3534]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=101.79.130.213
May 6 18:18:54 localhost sshd[3534]: Failed password for invalid user phil from 101.79.130.213 port 38354 ssh2
May 6 18:18:54 localhost sshd[3534]: Received disconnect from 101.79.130.213: 11: Bye Bye [preauth]
突出显示的部分是操作系统插入以提供更多上下文的标准模式。之后,iptables防火墙服务以多种不同的方式将失败尝试写入日志。
在上述前两行中,我们看到了两次单独的失败(PAM身份验证错误和密码错误)。过滤器中定义的正则表达式旨在匹配任何可能的失败行。您不应该调整这些行中的任何一个,但如果您曾经不得不自己创建一个过滤器文件,您应该意识到需要捕获所有表示未经授权使用错误的日志条目。
在底部,您可以看到一个ignoreregex
参数,目前为空白。这可用于排除更具体的模式,通常会匹配失败条件,以便在某些情况下要对fail2ban的失败触发器进行否定。我们不会调整这个。
当您完成检查后,请保存并关闭文件。
检查操作文件
现在,让我们来看看操作文件。该文件负责设置防火墙的结构,允许修改以封禁恶意主机,并根据需要添加和删除这些主机。
我们SSH服务调用的动作称为iptables-multiport
。现在打开相关联的文件:
去掉评论后,该文件看起来是这样的:
[INCLUDES]
before = iptables-blocktype.conf
[Definition]
actionstart = iptables -N fail2ban-<name>
iptables -A fail2ban-<name> -j RETURN
iptables -I <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
actioncheck = iptables -n -L <chain> | grep -a 'fail2ban-<name>[ \t]'
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
[Init]
name = default
port = ssh
protocol = tcp
chain = INPUT
文件首先引用另一个名为iptables-blocktype.conf
的动作文件,该文件定义了blocktype
参数,该参数配置了当客户端被禁止时将设置的限制。默认情况下,blocktype
设置为拒绝数据包,并回复被禁止客户端发送的ping请求,消息内容为端口不可达。我们将在下面的封禁规则中使用这个参数。
接下来,我们来看规则定义本身。 actionstart
动作在启动fail2ban服务时设置iptables防火墙。它创建一个新的链,向该链添加一条规则以返回到调用链,然后在INPUT链的开头插入一条规则,将匹配正确协议和端口目的地的流量传递到新的链中。
它通过使用我们在jail.local
文件中定义的action
中传入的值来实现这一点。 name
取自每个服务的部分标题。 chain
、protocol
和port
取自该文件中的action
行本身。
在这里,所有由其他文件设置的参数都通过在尖括号中包含参数名来引用:
<param_name>
当我们转到相应的actionstop
定义时,我们可以看到防火墙命令正在执行对actionstart
命令的反向操作。当Fail2ban服务停止时,它会干净地移除它添加的任何防火墙规则。
另一个名为actioncheck
的操作确保在尝试添加禁止规则之前已创建了正确的链。
接下来,我们来看实际的禁止规则,名为actionban
。该规则通过向我们创建的链中添加新规则来工作。该规则匹配违规客户端的源IP地址 – 当达到maxretry
限制时,此参数从授权日志中读取。它执行我们在文件顶部的[INCLUDE]
部分中获取的blocktype
参数定义的阻止。
actionunban
规则会移除此规则。当封禁时间到期时,fail2ban会自动执行此操作。
最后,我们来到[Init]
部分。这只是在没有传入所有适当值的情况下调用操作文件时提供一些默认值。
Fail2ban服务如何处理配置文件以实施封禁
现在我们已经了解了具体细节,让我们来看看fail2ban启动时发生的过程。
加载初始配置文件
首先,主要的fail2ban.conf
文件被读取,以确定主进程应该在什么条件下运行。如果需要,它会创建套接字、pid和日志文件,并开始使用它们。
接下来,fail2ban读取jail.conf
文件以获取配置详情。然后按字母顺序读取jail.d
目录中以.conf
结尾的任何文件。它将这些文件中找到的设置添加到其内部配置中,新值优先于jail.conf
文件中描述的值。
然后,它搜索jail.local
文件并重复此过程,适应新值。最后,它再次搜索jail.d
目录,按字母顺序读取以.local
结尾的文件。
在我们的情况下,我们只有一个jail.conf
文件和一个jail.local
文件。在我们的jail.local
文件中,我们只需要定义与jail.conf
文件不同的值。现在,fail2ban进程已经将一系列指令加载到内存中,这些指令代表了它找到的所有文件的组合。
它检查每个部分并搜索 enabled = true
指令。如果找到一个,它将使用在该部分下定义的参数来构建策略,并决定需要执行的操作。如果在服务部分中找不到参数,则使用 [DEFAULT]
部分中定义的参数。
解析动作文件以确定起始动作
Fail2ban 搜索 action
指令以确定要调用哪个动作脚本来实施封禁/解封策略。如果找不到,则回退到上面确定的默认动作。
action 指令包括将要读取的动作文件的名称,以及传递给这些文件所需参数的键值字典。这些值通常采用参数替换的形式,通过引用服务部分中配置的设置来实现。”name” 键通常传递特殊 __name__
变量的值,该变量将设置为部分标题的值。
然后,Fail2ban 使用这些信息在 action.d
目录中查找关联的文件。首先,它查找以 .conf
结尾的关联动作文件,然后使用任何包含在同一 action.d
目录中的附带 .local
文件中包含的设置修改找到的信息。
它解析这些文件以确定需要采取的操作。它读取actionstart
值以查看设置环境所需的操作。这通常包括创建防火墙结构以适应未来的禁止规则。
此文件中定义的操作使用从action
指令传递的参数。它将使用这些值动态创建适当的规则。如果某个变量未设置,它可以查看操作文件中设置的默认值来填补空白。
解析过滤文件以确定过滤规则
在jail.*
文件中,服务的参数还包括日志文件的位置以及应该用于检查文件的轮询机制(这由backend
参数定义)。它还包括应该用于确定日志中的某一行是否表示失败的过滤器。
Fail2ban在filter.d
目录中查找以.conf
结尾的匹配过滤器文件。它读取此文件以定义可用于匹配违规行的模式。然后,它搜索以.local
结尾的匹配过滤器文件,以查看是否有任何默认参数被覆盖。
它在读取服务日志文件时使用这些文件中定义的正则表达式。它会针对服务日志文件中的每一行,尝试每个在filter.d文件中定义的failregex行。
如果正则表达式返回匹配项,它会检查该行是否符合ignoreregex定义的正则表达式。如果也匹配,则fail2ban会忽略它。如果该行与failregex中的表达式匹配,但与ignoreregex中的表达式不匹配,则为引起该行的客户端增加一个内部计数器,并创建与该事件相关联的时间戳。
当由jail.*文件中的findtime参数设置的时间窗口已达到(由事件时间戳确定)时,内部计数器将再次递减,并且该事件不再被视为与禁止策略相关。
如果随着时间的推移记录了其他认证失败,每次尝试都会增加计数器。如果计数器在配置的时间窗口内达到maxretry参数设置的值,则fail2ban通过调用服务的action.d/文件中定义的actioncheck动作来实施禁止。这是为了确定actionstart动作是否设置了必要的结构。然后调用actionban动作来禁止违规客户端。它也为此事件设置了一个时间戳。
当经过了由bantime参数指定的时间后,fail2ban通过调用actionunban动作来解除对客户端的禁止。
结论
到目前为止,您对fail2ban的运作方式有了相当深入的了解。当您偏离标准配置时,了解fail2ban的运作方式有助于以可预测的方式操纵其行为。
要了解如何使用fail2ban保护其他服务,请阅读如何在Ubuntu 22.04上使用Fail2Ban保护Nginx服务器。