跳转至

接口遭遇高频恶意请求

01 前置理论与核心机制

1. Nginx 黑名单的局限性

  • 性能损耗:当接口遭遇高频恶意请求(CC 攻击)时,若仅通过脚本分析日志并将 IP 写入 Nginx 的 deny 配置再执行 reload,属于被动防御。恶意请求依然会完成 TCP 三次握手并进入 Nginx 的 Worker 进程,持续消耗应用层的连接数与 CPU 资源。
  • 维护成本:自定义脚本通常缺乏完善的生命周期管理,随着封禁列表不断膨胀,容易造成永久误封,后期人工维护成本较高。

2. Fail2Ban 的内核层拦截机制

  • 底层阻断:Fail2Ban 的核心逻辑是动态管理防火墙规则。它通过实时监控 Nginx、SSH 等服务日志,一旦检测到触发阈值的异常行为,会直接调用 Linux 内核防火墙(如 iptables、nftables 或 firewalld)下发拦截策略。
  • 资源保护:恶意 IP 的后续请求在网络层(进入 Nginx 之前)就会被内核直接丢弃(DROP),无法建立 TCP 握手,从而有效保护应用层资源,且支持到期自动解封。

02 故障排查与恢复闭环 SOP

遇到单 IP 高频请求接口的情况,可按以下步骤配置并启用 Fail2Ban 实现自动化封禁:

步骤一:定义触发阈值与规则配置

在 /etc/fail2ban/jail.local 中定义监控目标与触发边界:

# /etc/fail2ban/jail.local
[nginx-cc]
enabled = true
# 指定使用的过滤器规则 (需提前在 filter.d 中定义匹配正则)
filter = nginx-cc
# 指定要监控的 Nginx 访问日志路径
logpath = /var/log/nginx/access.log
# 统计时间窗口:10秒内
findtime = 10
# 触发阈值:请求超过 200 次
maxretry = 200
# 封禁时长:3600 秒 (1小时)
bantime = 3600
  • 规则说明:实时监控 Nginx 访问日志,若同一 IP 在 10 秒内的请求次数超过 200 次,则自动封禁该 IP 1 小时。

步骤二:启动服务并加载规则

启动服务,使 Fail2Ban 开始监控日志并接管防火墙规则:

systemctl start fail2ban
# 查看当前运行状态与生效的策略(Jail)列表
fail2ban-client status

步骤三:验证防火墙底层规则

当恶意请求触发阈值后,可在操作系统的底层防火墙中验证拦截规则是否生效:

# 查看 iptables 中 Fail2Ban 动态创建的拦截链
iptables -nL f2b-nginx-cc

  • 观测点:可以看到异常 IP 已被自动添加至 DROP 或 REJECT 规则中。后续流量将在系统网络协议栈边缘被直接拦截。到达设定的 1 小时封禁时间后,Fail2Ban 会自动移除该条目,无需人工干预。

步骤四:应对误判的快速解封 (Unban)

若线上发生误判,封禁了正常的业务网关或合作方 IP,无需修改配置文件或重启服务,可直接通过客户端命令即时解封:

# 从 nginx-cc 策略中手动解封指定的 IP
fail2ban-client set nginx-cc unbanip 192.168.0.89

  • 处置结果:底层 iptables 对应的拦截规则被同步移除,业务流量立即恢复正常。

03 真实生产故障现象与案例库

案例 1:恶意爬虫高频抓取商品数据

  • 故障现象:电商平台商品详情接口 CPU 使用率飙升,Nginx 日志显示数个固定 IP 正以每秒上百次的频率遍历商品 ID。
  • 排查与处置:属于典型的单点高频 CC 攻击。配置 Fail2Ban 监控 access.log,设定 findtime=10、maxretry=100。策略生效后数秒内,爬虫 IP 被全部加入 iptables 拒绝列表,服务器 CPU 负载迅速从 90% 回落至 15%。

案例 2:NAT 出口 IP 被误封导致办公网访问中断

  • 故障现象:内部测试团队反馈,办公网环境下所有人员均无法访问测试环境接口,提示连接超时。
  • 排查与处置:测试团队正在执行自动化并发压测,由于办公网共用同一个公网 NAT 出口 IP,该公共 IP 触发了 Fail2Ban 的限频阈值被封禁。处置方案为执行 unbanip 解除封禁,并在 jail.local 中配置 ignoreip = 192.168.x.x/24(或对应公网出口 IP),将可信网段加入白名单。

案例 3:大规模分布式 CC 攻击导致 Fail2Ban 资源耗尽

  • 故障现象:遭遇大规模代理池发起的分布式 CC 攻击,数万个不同 IP 并发请求,单 IP 请求频率较低但总吞吐量极大。服务器磁盘 I/O 满载,Fail2Ban 进程 CPU 占用率达到 100%。
  • 根因与处置:Fail2Ban 依赖单机日志正则匹配。面对海量日志与高频的 iptables 规则插入,其自身开销会耗尽系统资源。处置方案为立即停止单机 Fail2Ban(systemctl stop fail2ban),将流量切换至高防 CDN,依托云端 WAF 进行分布式流量清洗与人机校验。

04 生产排障速查表(高频请求与自动封禁)

遇到接口遭受高频恶意请求,按序执行以下防御动作:顺序操作层级执行命令 / 观测点动作定性处置动作01攻击确认tail -f /var/log/nginx/access.log日志高频刷新,存在明显的少数异常高频 IP确认符合单点高频 CC 攻击特征02规则配置编辑 /etc/fail2ban/jail.local设定统计时间窗口与触发阈值配置针对 Nginx 日志的拦截策略03启动服务systemctl restart fail2ban启动守护进程接管日志分析开启自动化内核层拦截04拦截核验fail2ban-client status nginx-cc查看当前被封禁的 IP 总数与列表确认底层 iptables 规则已下发生效05误判解封fail2ban-client set nginx-cc unbanip <IP>收到正常业务方反馈 IP 被误封手动解除指定 IP 的封禁状态,恢复业务06白名单加固修改配置中的 ignoreip 字段避免办公网、压测节点或内网网关被误封将可信网段加入白名单并重载服务

05 面试应答参考话术

面试官问:“如果线上接口被恶意高频请求,导致服务器负载飙升,你通常怎么处理?”

可以这样回答:如果仅通过编写脚本分析 Nginx 日志,再将异常 IP 写入 Nginx 的 deny 配置文件中,这种方案相对被动。因为恶意请求依然会完成 TCP 三次握手并进入 Nginx 进程,持续消耗应用层资源,且黑名单列表需要人工维护。我的核心处理原则是:将拦截动作下沉至内核防火墙层面,并确保具备自动过期释放机制。针对少数恶意 IP 发起的高频并发请求,我会使用 Fail2Ban 工具动态管理防火墙。 只需在配置文件中定义触发规则,例如监控 Nginx 访问日志,若同一 IP 在 10 秒内请求超过 200 次,则自动封禁 3600 秒。 Fail2Ban 检测到异常后,会直接将该 IP 加入 Linux 的 iptables 防火墙拒绝列表。后续请求在进入 Nginx 之前,就会在内核网络栈被直接丢弃(DROP)。1 小时后系统会自动解除封禁,无需人工介入。若发生误判封禁了正常业务 IP,也无需修改配置重启服务,直接执行 fail2ban-client set <策略名> unbanip <IP> 即可立即恢复访问。同时需要区分攻击规模:单机 Fail2Ban 非常适合应对固定 IP 的高频抓取;但如果遭遇海量动态代理 IP 发起的分布式 CC 攻击,单机进行日志正则匹配和频繁修改 iptables 会消耗大量 CPU 资源,此时必须将流量切换至云端 WAF 或高防 CDN 在边缘节点进行流量清洗。

06 面试高频追问与应答预案

追问 1:为什么在 iptables 层拦截,比在 Nginx 层使用 deny 或 limit_req 性能更好?

  • 回答要点:
  • 所处网络栈层级不同:Nginx 工作在应用层(OSI 第七层)。请求到达 Nginx 前,操作系统内核必须先完成完整的 TCP 三次握手、分配 Socket 文件描述符,并将数据从内核态拷贝至用户态,这一过程本身就会消耗 CPU 与内存资源。
  • 内核态直接丢弃:iptables 工作在内核网络协议栈底层(Netfilter 框架)。当数据包到达网卡触发中断后,在路由前或进入本地协议栈时(PREROUTING / INPUT 链)即可直接匹配规则并执行 DROP。既不建立 TCP 连接,也不消耗用户态进程资源,性能损耗极低。

追问 2:如果 Nginx 部署在负载均衡(如 AWS ALB / 阿里云 SLB)后端,直接使用 Fail2Ban 会有什么问题?

  • 回答要点:
  • 潜在问题:在负载均衡架构下,与 Nginx 建立物理 TCP 连接的是负载均衡器的内网 IP。若配置不当,Fail2Ban 可能会直接将负载均衡器的内网 IP 封禁,导致所有正常业务流量全部中断。
  • 解决方案:首先需确保 Nginx 配置了 set_real_ip_from 和 real_ip_header X-Forwarded-For,使访问日志记录真实的客户端 IP。其次,由于单机 iptables 工作在网络层,只能拦截物理连接(即 LB 的连接),无法直接按报文内的 X-Forwarded-For 头进行拦截。因此在挂载了负载均衡的生产架构中,Fail2Ban 的执行动作(Action)不能配置为调用本地 iptables,而应配置为调用云厂商 SLB/WAF 的 API,或者改用 iptables 的 string 模块扩展匹配,将真实恶意 IP 在网关入口层进行封禁。

07 自动化状态巡检与数据统计脚本(check_fail2ban.sh)

该脚本用于快速查看当前服务器的 Fail2Ban 运行状态,提取各监控策略的封禁统计数据,并输出当前处于封禁状态的 IP 列表。

#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: check_fail2ban.sh
# 功能描述: 自动化巡检 Fail2Ban 防御状态与拦截统计 (纯只读)
# 使用方式: 在运行 Fail2Ban 的宿主机以 root 权限执行
# ==============================================================================

set -o pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'

echo -e "${BLUE}================================================================${PLAIN}"
echo -e "${BLUE}           Fail2Ban 自动防御系统状态巡检与统计                  ${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"

# 1. 检查服务存活状态
if ! command -v fail2ban-client &>/dev/null; then
    echo -e "${RED}[ERROR] 当前系统未安装 Fail2Ban。${PLAIN}"
    exit 1
fi

F2B_STATUS=$(systemctl is-active fail2ban 2>/dev/null)
if [ "$F2B_STATUS" == "active" ]; then
    echo -e "${YELLOW}[1/3] 服务状态: ${GREEN}[运行中]${PLAIN}"
else
    echo -e "${YELLOW}[1/3] 服务状态: ${RED}[未运行] ($F2B_STATUS)${PLAIN}"
    exit 1
fi

# 2. 提取配置的 Jail 列表
JAILS=$(fail2ban-client status | grep "Jail list:" | sed -e 's/.*Jail list://' -e 's/,//g')
if [ -z "$JAILS" ]; then
    echo -e "${YELLOW}[INFO] 当前未启用任何监控策略 (Jail)。${PLAIN}"
    exit 0
fi

echo -e "\n${YELLOW}[2/3] 当前生效的防御策略 (Jails):${PLAIN}"
for jail in $JAILS; do
    echo -e "  -> ${GREEN}${jail}${PLAIN}"
done

# 3. 统计拦截数据与被封禁 IP 列表
echo -e "\n${YELLOW}[3/3] 防御拦截数据统计明细:${PLAIN}"
TOTAL_BANNED=0

for jail in $JAILS; do
    STATUS_OUT=$(fail2ban-client status "$jail")

    # 提取关键数据
    CURRENT_BANNED=$(echo "$STATUS_OUT" | grep "Currently banned:" | awk '{print $4}')
    TOTAL_JAIL_BANNED=$(echo "$STATUS_OUT" | grep "Total banned:" | awk '{print $4}')
    BANNED_IPS=$(echo "$STATUS_OUT" | grep "Banned IP list:" | sed -e 's/.*Banned IP list://')

    echo -e "${BLUE}----------------------------------------------------------------${PLAIN}"
    echo -e " 策略名称         : ${GREEN}$jail${PLAIN}"
    echo -e " 当前被封禁 IP 数 : ${RED}${CURRENT_BANNED}${PLAIN}"
    echo -e " 历史累计封禁次数 : ${YELLOW}${TOTAL_JAIL_BANNED}${PLAIN}"

    if [ "$CURRENT_BANNED" -gt 0 ]; then
        echo -e " 当前封禁 IP 列表 :\n  ${BANNED_IPS}"
    fi

    TOTAL_BANNED=$((TOTAL_BANNED + CURRENT_BANNED))
done

echo -e "${BLUE}----------------------------------------------------------------${PLAIN}"

if [ "$TOTAL_BANNED" -gt 0 ]; then
    echo -e "${RED}[提示] 当前共有 ${TOTAL_BANNED} 个 IP 处于封禁状态。${PLAIN}"
    echo -e "若需手动解除误封,请执行: ${YELLOW}fail2ban-client set  unbanip ${PLAIN}"
else
    echo -e "${GREEN}[OK] 当前暂无处于封禁状态的异常 IP。${PLAIN}"
fi

echo -e "${BLUE}================================================================${PLAIN}"