Web服务器跑起来之后,Nginx的access.log就像黑匣子一样不停记录着每一次握手与响应。攻击者在踩点阶段通常不会直接发动猛烈进攻,而是先用批量请求探测存在哪些接口、用了什么参数、是否暴露了后台路径。这些试探性行为会在日志里留下和正常用户截然不同的痕迹,能不能看出来,取决于你有没有掌握对应的识别逻辑。

从默认日志格式看异常请求结构
Nginx最常用的combined日志格式包含了远程地址、请求行、状态码、来源页、客户端标识和响应大小。正常用户的请求路径大多集中在业务路由上,例如/api/login或者/product/123,而攻击者的路径里经常出现/admin.php、/.env、/phpmyadmin/这类敏感入口。当这类路径返回404或403却高频出现时,基本可以判断有人在自动化扫描。
除了路径,User-Agent字段也是重要线索。很多扫描器如sqlmap、AWVS会使用自带标识,而正常浏览器一般是Mozilla开头的一段复杂描述。如果看到User-Agent为空的请求大量访问接口,或者是同一个UA在每秒内发起几十次不同路径请求,就很有可能是脚本行为。我们可以在日志中通过正则匹配这些特征,把可疑行单独导出分析。
响应时间同样能反映问题。正常页面渲染可能在几十到几百毫秒,但若某次请求触发了服务端错误导致死循环,或者攻击者用慢速POST耗尽连接,日志里的request_time会明显拉长。把超过两秒的请求挑出来看,往往能发现隐藏的恶意上传或命令执行尝试。
# 提取访问敏感路径且返回404的IP和路径
awk '$7 ~ //(admin|phpmyadmin|.env|wp-login)/ && $9 == 404 {print $1, $7}' access.log | sort | uniq -c | sort -rn | head
典型攻击在日志中的特征模式
SQL注入攻击通常把恶意的union select、' or 1=1--等片段拼在URL参数里。由于Nginx记录的是完整请求行,我们可以直接搜索这些关键字。要注意的是,部分攻击者会对_payload做URL编码,比如把空格变成%20,把单引号变成%27,所以识别时既要匹配明文也要匹配编码形态。下面这段脚本演示了如何同时捕获两种形态。
目录遍历和文件读取攻击则喜欢用../或者%2e%2e/试图跳出网站根目录。如果日志里某个IP反复请求/../../etc/passwd这类路径,即便服务器没有解析成功,也说明对方在试权限。另外,针对Java服务的反序列化攻击常把长串乱码放在POST体,虽然access.log默认不记请求体,但异常的超长URL和固定的几个脆弱路径(如/invoker/readonly)足以作为告警依据。
撞库和暴力破解体现在同一账号或同一IP对登录接口的密集访问。假设正常用户一天登录不到十次,而某IP在一分钟内对/login发起了上百次POST且大量返回401,那基本是机器在跑密码字典。我们可以按IP聚合登录失败次数,超过阈值就封禁。
# 识别常见SQL注入关键字(含URL编码) grep -E "union%20select|' or 1=1|%27%20or%201=1" access.log > sqli_hits.log # 识别目录遍历 grep -E "../|%2e%2e%2f" access.log > traversal_hits.log
用自动化工具实现实时拦截与响应
靠人肉翻日志终究跟不上攻击速度,fail2ban这类工具可以读取Nginx日志,按我们写好的过滤规则统计某IP在指定时间窗口内的异常次数,一旦超标就调用iptables丢弃它的包。它的核心是filter配置,里面用正则描述上面提到的攻击特征,例如匹配返回401过多的登录爆破。
配置时建议把不同攻击类型拆成独立jail,比如nginx-sqli、nginx-traversal、nginx-badbots,分别设置bantime和findtime。这样既能精准打击,也避免误封正常爬虫。下面给出一个最简化的filter示例,用于封锁频繁探测敏感文件的IP。
除了fail2ban,也可以把日志推到ELK或Loki,用可视化面板看实时趋势。当某个时间段突然出现大量404扫描,系统可自动发钉钉或邮件告警。不过无论哪种方案,底层都依赖我们前面梳理出的特征规则,规则写得准,响应才快。
[nginx-traversal] enabled = true filter = nginx-traversal action = iptables-multiport[name=nginx, port="80,443"] logpath = /var/log/nginx/access.log maxretry = 5 findtime = 60 bantime = 3600 [Definition] failregex = ^<HOST> -.*"(GET|POST).*(../|%2e%2e).*"
把日志分析变成日常运维的闭环,才能让Nginx不仅做流量入口,也兼当前哨站。从看一条异常记录开始,慢慢积累属于自己业务的特征库,比任何通用防火墙都更贴合实际环境。