系统环境

操作系统:CentOS 7.9

Nginx响应头

编辑nginx配置文件:

sudo vim /etc/nginx/nginx.conf

在http部分server上方增加如下配置:

# 全局隐藏 Server 头信息
server_tokens off;
# 基础安全头
add_header X-Content-Type-Options    "nosniff";
add_header X-Frame-Options          "SAMEORIGIN";
# 启用浏览器的 XSS 过滤器,并阻止渲染疑似 XSS 的页面
# (注:现代浏览器已逐步废弃此头,推荐改用 CSP - Content-Security-Policy 的防护策略)
add_header X-XSS-Protection         "1; mode=block";

说明:可以使用 SecurityHeaders.com 扫描你的网站查看安全头情况。

Nginx请求限速

编辑nginx配置文件:

sudo vim /etc/nginx/nginx.conf

在http部分server上方增加如下配置:

# 定义全局请求速率限制区域
# 10MB内存空间,限制每个IP每秒20个请求
limit_req_zone $binary_remote_addr zone=req_perip:10m rate=20r/s;
# 可以定义多个区域用于不同限制级别
limit_req_zone $binary_remote_addr zone=strict_req_perip:10m rate=5r/s;

# 定义全局连接数限制区域
# 10MB内存空间,跟踪每个IP的连接
limit_conn_zone $binary_remote_addr zone=conn_perip:10m;

说明:

  • 1MB内存大约可以存储16,000个IP的状态

  • 10MB区域可以处理约160,000个唯一IP

创建配置文件/etc/nginx/conf.d/rate_limits.conf.included

# 请求速率限制,突发50个请求
limit_req zone=req_perip burst=50 nodelay;

# 连接数限制,每个IP最多10个并发连接
limit_conn conn_perip 10;

# 超限制返回429(Too Many Requests)状态码
limit_req_status 429;
limit_conn_status 429;

在需要的server块中包含它:

server {
    include /etc/nginx/conf.d/rate_limits.conf.included;
    # 其他server配置...
}

Fail2Ban

Fail2Ban 是一款开源工具,用于监控系统日志并自动禁止表现出恶意行为的IP地址(如多次登录失败、暴力破解等)。

安装 Fail2Ban

# 安装
sudo yum install fail2ban
# 启动并设置开机自启
sudo systemctl enable --now fail2ban

基础配置

主配置文件位于 /etc/fail2ban/jail.conf,但建议创建本地配置文件避免升级被覆盖:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo vim /etc/fail2ban/jail.local

关键全局配置项:

[DEFAULT]
# 禁止IP的持续时间
bantime = 24h

# 触发禁令的失败次数
maxretry = 5

# 检测时间窗口
findtime = 10m

# 通过iptables封禁
banaction = iptables-multiport

# 白名单IP(多个IP用空格分隔)
ignoreip = 127.0.0.1/8 192.168.1.0/24

常用服务保护配置

SSH保护(默认已启用)

[sshd]
enabled = true
# 解决启动警告:WARNING [sshd] Simulate NOW in operation since found time has too large deviation...
# 解决启动警告:WARNING [sshd] Please check jail has possibly a timezone issue. Line with odd timestamp...
# 在确保服务器时区是CST的情况下(可通过timedatectl查看)
timezone = Asia/Shanghai

Nginx防护

[nginx-http-auth]
enabled = true

# 配合Nginx请求限速
[nginx-limit-req]
enabled = true
maxretry = 3
findtime = 5m

[nginx-botsearch]
enabled = true
logpath = %(nginx_access_log)s
          %(nginx_error_log)s

自定义过滤器

比如自定义过Nginx的access日志格式,此时Fail2ban默认的配置就不起作用。比如有如下自定义格式:

log_format  json  '{'
		    	'"time_local": "$time_local", ' # 请求服务器时间 Default
		    	'"remote_addr": "$remote_addr", ' # 客户端IP地址 Default
		    	'"remote_user": "$remote_user", ' # 已经经过HTTP基础认证的用户名 Default
		    	'"request": "$request", ' # 请求行 (METHOD + URI + HTTP版本) Default
		    	'"status": "$status", ' # HTTP状态码 Default
		    	'"body_bytes_sent": "$body_bytes_sent", ' # 发送给客户端的响应body字节数,响应头不计算在内 Default
		    	'"request_time": "$request_time", ' # 整个请求的总时间,请求处理时间,单位为秒,精度毫秒; 从读入客户端的第一个字节开始,直到把最后一个字符发送给客户端后进行日志写入为止 Default
		    	'"http_referer": "$http_referer", ' # 记录此次请求是从哪个链接访问过来的,可以根据 refer 进行防盗链设置 Default
		    	'"http_user_agent": "$http_user_agent", ' # 记录客户端访问信息,例如:浏览器,手机客户端等 Default
		    	'"http_x_forwarded_for": "$http_x_forwarded_for", ' # 客户端的真实 ip,通常 web 服务器放在反向代理的后面,这样就不能获取到客户的 IP 地址了,通过$remote_add 拿到的 IP 地址是反向代理服务器的 iP 地址。反向代理服务器在转发请求的 http 头信息中,可以增加 x_forwarded_for 信息,用以记录原有客户端的 IP 地址和原来客户端的请求的服务器地址 Default
		    	'"scheme": "$scheme", ' # 请求使用的Web协议,”http” 或 “https”
		    	'"host": "$host", ' # 请求主机头字段,否则为服务器名称
		    	'"http_host": "$http_host", ' # 请求地址,即浏览器中输入的地址(IP 或域名)
		    	'"server_name": "$server_name", ' # 服务器名称
		    	'"upstream_addr": "$upstream_addr", ' # 当 ngnix 做负载均衡时,可以查看后台提供真实服务的设备
		    	'"upstream_response_time": "$upstream_response_time", ' # 请求过程中,upstream 响应时间
		    	'"upstream_status": "$upstream_status", ' # upstream 状态,比如成功是 200
		    	'"request_id": "$final_request_id"' # 获取final_request_id
		    '}';

创建过滤器文件(复制原nginx-botsearch配置文件):

sudo cp /etc/fail2ban/filter.d/nginx-botsearch.conf /etc/fail2ban/filter.d/nginx-botsearch-ym.local

编辑文件/etc/fail2ban/filter.d/nginx-botsearch-ym.localfailregexdatepattern部分匹配自定义的日志格式:

# Fail2Ban filter to match web requests for selected URLs that don't exist
#

[INCLUDES]

# Load regexes for filtering
before = botsearch-common-ym.local

[Definition]

failregex = ^.*\"remote_addr\": \"<HOST>\".*\"request\": \"(GET|POST|HEAD) \/<block> \S+\".*\"status\": \"404\".*$
	    ^.*\"remote_addr\": \"<HOST>\".*\"status\": \"444\".*$
	    ^ \[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+\"\, .*?$

ignoreregex = 

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

# DEV Notes:
# Based on apache-botsearch filter
# 
# Author: Frantisek Sumsal

说明:failregex<HOST>是Fail2ban的关键,即IP。datepattern({DATE})是 Fail2Ban 内置的 DATE 模式(通用日期格式,示例:01/Jan/2024:12:00:00 +0800)。

jail.local中配置启用过滤器:

[nginx-botsearch]
enabled = true
filter = nginx-botsearch-ym
logpath = %(nginx_access_log)s
          %(nginx_error_log)s

操作命令

# 测试filter正则匹配
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-botsearch-ym.conf

# 查看运行状态
sudo fail2ban-client status

# 查看具体jail状态
sudo fail2ban-client status sshd
sudo fail2ban-client status nginx-botsearch

# 手动封禁IP
sudo fail2ban-client set sshd banip 192.168.1.100

# 手动解封IP
sudo fail2ban-client set nginx-botsearch unbanip 192.168.1.100

# 重新加载配置
sudo fail2ban-client reload
# 重启fail2ban
sudo systemctl restart fail2ban

# 查看日志
sudo tail /var/log/fail2ban.log

# 查看iptables
sudo iptables -L -n --line-numbers

邮件通知

Fail2ban默认使用sendmail发送邮件,由于配置复杂程度等原因这里使用自带的mail进行邮件发送。

配置mail

以QQ邮箱为例,其他邮箱可参考自行实现。

使用465端口,使用以下命令生成并设置证书:

mkdir -p /root/.certs/
 
# 申请证书
echo -n | openssl s_client -connect smtp.qq.com:465 | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/.certs/qq.crt
 
# 添加证书到证书数据库
certutil -A -n "GeoTrust SSL CA" -t "C,," -d ~/.certs -i ~/.certs/qq.crt
certutil -A -n "GeoTrust Global CA" -t "C,," -d ~/.certs -i ~/.certs/qq.crt
 
# 信任证书
cd /root/.certs/
certutil -A -n "GeoTrust SSL CA - G3" -t "Pu,Pu,Pu" -d ~/.certs/./ -i qq.crt
 
# 列出目录下证书
certutil -L -d /root/.certs

备份并编辑mail配置文件:

sudo mv /etc/mail.rc /etc/mail.rc.bak
sudo vim /etc/mail.rc

编辑内容:

# 邮箱账号
set smtp-auth-user=xxx@qq.com
# 发信人邮箱
set from=xxx@xxx.com
# 邮箱密码/授权码
set smtp-auth-password=password
# 证书目录
set nss-config-dir=/root/.certs
# 认证方式
set smtp-auth=login
set smtp=smtps://smtp.qq.com:465
set ssl-verify=ignore

测试邮件发送:

echo "邮件内容" | sudo mail -v -s "邮件标题" your-email@qq.com

配置Fail2ban

编辑jail.local,在DEFAULT下增加邮件配置:

[DEFAULT]
# 邮件配置
sender = your-email@qq.com
destemail = dest@example.com
mta = mail
# 封禁+记录日志+发邮件
action = %(action_mwl)s

通过复制sendmail的配置文件创建对应的mail-whois-lines配置文件并编辑:

sudo cp /etc/fail2ban/action.d/sendmail-whois-lines.conf /etc/fail2ban/action.d/mail-whois-lines.local
sudo vim /etc/fail2ban/action.d/mail-whois-lines.local

调整后内容如下:

# Fail2Ban configuration file
#
# Author: Cyril Jaquier
#
#

[INCLUDES]

before = mail-whois-common.conf
         helpers-common.conf

[Definition]

# bypass ban/unban for restored tickets
norestored = 1

# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    See jail.conf(5) man page
# Values:  CMD
#
actionban = ( printf %%b "警告!!!
 			  被攻击机器名:<fq-hostname>
 			  被攻击服务:<name>
 			  时间范围:<findtime> 内
 			  攻击次数:<failures> 次
 			  攻击者IP:<ip> 
			  封禁时间:<bantime> 秒\n";
	      printf %%b "失败日志(最多<grepmax>):\n";
	      %(_grep_logs)s; ) | mail -s "[<name>]新增拦截<ip>" <dest>

[Init]

# Default name of the chain
#
name = default

# Path to the log files which contain relevant lines for the abuser IP
#
logpath = /dev/null

# Number of log lines to include in the email
#
grepmax = 3
#grepopts = -m <grepmax>

重启Fail2ban后再次封禁即可收到邮件通知。