25 September 2014

Nginx 访问日志可以帮助发现某个来源短时间内发起大量请求,但“请求多”不等于恶意。共享出口、搜索引擎、监控和前端资源加载,都可能产生高访问量。先统计候选来源,再结合业务规则处置,比直接把 IP 写入防火墙更容易控制误伤。

将统计窗口固定下来

下面使用 Python 3,读取一份已经按时间范围切好的日志。假设每行首字段为客户端 IP,这需要与实际 Nginx log_format 对应。脚本只输出达到阈值的候选,不修改系统防火墙:

import argparse
import ipaddress
from collections import Counter

parser = argparse.ArgumentParser()
parser.add_argument("logfile")
parser.add_argument("--threshold", type=int, default=500)
args = parser.parse_args()
if args.threshold < 1:
    parser.error("threshold must be positive")

counts = Counter()
with open(args.logfile, encoding="utf-8", errors="replace") as stream:
    for line in stream:
        fields = line.split(maxsplit=1)
        if not fields:
            continue
        try:
            address = str(ipaddress.ip_address(fields[0]))
        except ValueError:
            continue
        counts[address] += 1

for address, count in counts.most_common():
    if count >= args.threshold:
        print(address, count)
python count_requests.py access-window.log --threshold 500

阈值的含义依赖窗口长度;一小时 500 次与一分钟 500 次完全不同。Counter 的内存也会随不同 IP 数增长,大规模日志可以交给日志平台或流式聚合处理。

真实 IP 与日志轮转

反向代理后的首字段可能是代理 IP。只有明确可信代理范围并正确配置 real_ip 后,才能按真实来源统计;不能无条件信任任意客户端传来的 X-Forwarded-For。

连续运行时不要只记住“上次读到第几行”:日志轮转、截断和进程重启都会使位置失效。应跟踪文件标识与偏移,或由日志采集器负责读取,把带时间戳的记录交给统计逻辑。

处置需要过期和恢复机制

可以先按路径、状态码、请求速率与业务结果判断异常,再选择限速、挑战或临时封禁。封禁应有理由、到期时间、允许列表和人工解除入口;不要让一个一次性脚本永久追加重复 DROP 规则。

Nginx 的 limit_req 更适合在请求进入时进行速率控制,但也需要选择正确的 key 和突发策略。上线前用正常用户与共享网络场景验证误伤率,再决定阈值。

参考:Nginx 日志模块、limit_req。





blog comments powered by Disqus
Fork me on GitHub