
您可能没有意识到,网站始终面临威胁。如果网站运行WordPress,机器人会尝试发送垃圾信息;如果网站设有登录页面,则会遭受暴力破解密码攻击。您也可能将搜索引擎爬虫视为不受欢迎的访客。
保护网站免受不必要、可疑和恶意活动的侵扰并非易事。Web应用防火墙,例如NGINX App Protect以及合作伙伴提供的NGINX Plus认证模块,都是有效的工具,必须将其纳入安全体系。对于大多数环境而言,安全防护越充分越好,而多层防护通常最为有效。
本文将介绍如何使用fail2ban,为Web安全体系增加一道防护。Fail2ban是一种入侵检测系统(IDS),可持续监控日志文件中的可疑活动,并执行一项或多项预配置操作。通常,fail2ban会监控登录失败尝试,并在一段时间内阻止(封禁)相关IP地址。这是一种简单而有效的暴力密码攻击防御措施。
在NGINX Plus R13中,我们引入了原生键值存储和新的NGINX Plus API。借助这些功能,外部系统可以驱动NGINX Plus的配置和行为,从而实现丰富的动态配置方案。本篇博文还将介绍如何使用fail2ban自动重新配置NGINX Plus,使其忽略来自多次触发身份验证失败事件的IP地址的请求。
编者注:在NGINX Plus R16及更高版本中,键值存储可在集群内的所有NGINX Plus实例之间同步。(其他NGINX Plus功能也支持在集群内共享状态。)有关详细信息,请参阅我们的博客和NGINX Plus管理指南。
使用键值存储维护 IP 拒绝列表
NGINX Plus键值存储是一种原生的内存存储,具有三个主要特性:
- 键值对表示为JSON对象
- 键值对完全通过API管理
- 这些值可作为常规配置变量供NGINX Plus使用
通过创建一个由keyval_zone指令命名的共享内存区来定义键值存储。然后,可以使用HTTP POST方法向API提交JSON对象,为该键值存储填充一组初始值。keyval指令随后会定义用作查找键的现有变量(示例中为$remote_addr),以及一个新变量的名称($num_failures),该变量的值将根据查找键对应的值计算得出。

通过将location块指定为NGINX Plus API端点来启用该API。
server {
listen 1111;
allow 127.0.0.1; # Only allow access from localhost,
deny all; # and prevent remote access.
location /api {
api write=on; # The NGINX Plus API endpoint in read/write mode
}
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;
server {
listen 80;
location / {
root /usr/share/nginx/html;
if ($num_failures) {
return 403;
}
}
}
vim: syntax=nginx在向其中添加键值对之前,请求获取denylist存储的内容会返回一个空的JSON对象。
$ curl http://localhost:1111/api/6/http/keyvals/denylist{}现在可以使用HTTP POST 方法(以 -d 参数的形式传递给 curl 命令)提交新的JSON对象,从而向键值存储中写入初始键值对。空的键值存储可以通过 POST 一次写入多个键值对,之后每次只能写入一个键值对。
$ curl -iX POST -d '{"10.0.0.1":"1"}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 201 Created
...要删除键值对,可对该键执行PATCH操作,并将其值设为null。
$ curl -iX PATCH -d '{"10.0.0.1":null}' http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...可以通过发送DELETE方法删除键值存储中的所有键值对。
$ curl -iX DELETE http://localhost:1111/api/6/http/keyvals/denylistHTTP/1.1 204 No Content
...现在可以配置简单的IP拒绝列表功能。
server {
listen 1111;
allow 127.0.0.1; # Only allow access from localhost,
deny all; # and prevent remote access.
location /api {
api write=on; # The NGINX Plus API endpoint in read/write mode
}
}
keyval_zone zone=denylist:1M;
keyval $remote_addr $num_failures zone=denylist;
server {
listen 80;
location / {
root /usr/share/nginx/html;
if ($num_failures) {
return 403;
}
}
}
vim: syntax=nginx此配置片段将端口 80 配置为Web服务器。第 18 行将$num_failures变量的值设为denylist共享内存区中与$remote_addr变量(客户端IP地址)对应的键相匹配的键值对的值。按顺序说明,可以更清楚地表达$num_failures的求值过程:
- 将
$remote_addr的值传递给denylist键值存储 - 如果“
$remote_addr”与某个键完全匹配,则获取该键值对的值 - 将该值作为
$num_failures 返回
因此,如果客户端IP地址已通过POST提交到键值存储,则所有请求都会返回HTTP 403 Forbidden错误。与使用map块实现相同目的相比,这种方法的优势在于,IP拒绝列表可由外部系统控制。
使用fail2ban动态管理IP拒绝列表
如上所述,fail2ban通常用于检测日志文件中的可疑和/或恶意活动,然后采取措施保护系统。默认操作是配置iptables,丢弃来自日志所记录IP地址的所有数据包。虽然这种方法能有效阻止恶意行为者,但会给正常用户带来很差的体验,尤其是在他们忘记密码时。在这些用户看来,网站似乎根本无法访问。
以下配置描述了一项fail2ban操作,该操作会将违规IP地址提交到NGINX Plus的拒绝列表键值存储中。随后,NGINX Plus会显示更有帮助的错误页面,同时对该IP地址实施请求速率限制。这样,如果相关行为属于攻击的一部分,就能有效遏制攻击。
假设fail2ban采用默认安装方式,与NGINX Plus安装在同一主机上,且所有配置均位于/etc/fail2ban目录下。
以下fail2ban操作使用NGINX Plus API,在denylist键值存储中添加和移除“被封禁”的IP地址,方式与上述简单示例相同。我们将nginx-plus-denylist.conf文件放在/etc/fail2ban/action.d目录中。
[Definition]
actionban = curl -s -o /dev/null -d '{"":""}' http://localhost:1111/api/6/http/keyvals/denylist
actionunban = curl -s -o /dev/null -X PATCH -d '{"":null}' http://localhost:1111/api/6/http/keyvals/denylist以下fail2ban jail会启用内置过滤器,用于检测通过NGINX的HTTP基本身份验证模块进行的失败登录尝试,并应用nginx-plus-denylist.conf中定义的操作。此外,还有许多其他内置过滤器可供使用,也可以轻松创建过滤器来检测NGINX访问日志中的异常活动。
[DEFAULT]
bantime = 120
banaction = nginx-plus-denylist
[nginx-http-auth]
enabled = true以下NGINX Plus配置片段将HTTP基本身份验证应用于默认的“Welcome to NGINX”页面。我们将password_site.conf文件放在/etc/nginx/conf.d目录中。
server {
listen 1111;
allow 127.0.0.1; # Only allow access from localhost,
deny all; # and prevent remote access.
location /api {
api write=on; # The NGINX Plus API endpoint in read/write mode
}
}
keyval_zone zone=denylist:1M state=denylist.json;
keyval $remote_addr $num_failures zone=denylist;
limit_req_zone $binary_remote_addr zone=20permin:10M rate=20r/m;
server {
listen 80;
root /usr/share/nginx/html;
location / {
auth_basic "closed site";
auth_basic_user_file users.htpasswd;
if ($num_failures) {
rewrite ^.* /banned.html;
}
}
location = /banned.html {
limit_req zone=20permin burst=100;
}
}
vim: syntax=nginx第 11 行定义了keyval_zone,与上文的denylist_keyval.conf相同,但增加了state参数。该参数指定用于存储键值存储状态的文件,因此在 NGINX Plus 停止并重启后,该状态仍会保留。即使未指定状态文件,键值存储也会在常规配置重新加载后保留。(第 1–10 行未显示,但与denylist_keyval.conf中的内容相同。)
第14行创建了一个名为20permin的共享内存区,并指定每个客户端IP地址的最大请求速率为每分钟20个请求。当fail2ban将IP地址添加到拒绝列表时(第30行),系统会对该IP地址应用此速率限制。
The server块定义了一个侦听默认端口(80)的Web服务器,其中所有请求均由location /块(第20行)匹配。root指令(第18行)指定Web内容所在的位置。第21至22行指定此站点需要使用HTTP基本身份验证,并且授权用户的密码数据库位于文件users.htpasswd中。auth_basic_user_file指令的文档介绍了该文件的格式。请注意,此配置未加密,仅用于测试;任何使用HTTP基本身份验证的生产网站都应使用SSL/TLS来保障传输安全。
当客户端IP地址已被列入拒绝名单时(第24–26行),系统不会返回HTTP 403错误代码,而是将请求重写为/banned.html,随后由与该URI完全匹配的location块进行处理(第29行)。系统将返回一个静态网页,说明用户的IP地址因登录失败次数过多而被阻止访问。此外,系统还会应用严格的速率限制(第30行),防止恶意客户端无谓地消耗系统资源。

(此示例网页的HTML可在本文的GitHub存储库中找到,文件名为banned.html。)
可以按如下方式模拟连续登录失败及相应的fail2ban活动。
$ curl -i http://admin:password@www.example.com/HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:admin@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:pass1234@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:letmein@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl -i http://admin:fred@www.example.com/
HTTP/1.1 401 Unauthorized ...
$ curl http://admin:P@ssw0rd@www.example.com/
<!DOCTYPE html>
<html>
<head>
<title>Banned</title>
...
$ tail -f /var/log/fail2ban.log
2017-09-15 13:55:18,903 fail2ban.filter [28498]: INFO [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:55:28,836 fail2ban.filter [28498]: INFO [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:49,228 fail2ban.filter [28498]: INFO [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:50,286 fail2ban.filter [28498]: INFO [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,015 fail2ban.filter [28498]: INFO [nginx-http-auth] Found 172.16.52.1
2017-09-15 13:57:52,088 fail2ban.actions [28498]: NOTICE [nginx-http-auth] Ban 172.16.52.1
2017-09-15 13:59:52,379 fail2ban.actions [28498]: NOTICE [nginx-http-auth] Unban 172.16.52.1现在,无需进一步更改配置,即可运行此动态IP拒绝列表解决方案。Fail2ban会监控NGINX日志文件,并通过API将被封禁的IP地址添加到NGINX Plus键值存储中。120秒后(即bantime在jail.local中配置的时长),系统会再次通过NGINX Plus API将违规IP地址从拒绝列表中移除,并重新接受来自该地址的登录尝试。
NGINX Plus API和键值存储支持此类集成。现在,无需创建NGINX Plus配置文件并执行重新加载,即可实现高级配置解决方案。欢迎分享您如何使用这些功能创建自己的动态配置解决方案。请在下方留言。
如需试用NGINX Plus,立即开始30天免费试用,或联系我们,讨论您的使用场景。
关于作者

相关博客文章
NGINX Plus R26 正式发布
NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。
