目录遍历扫描是攻击者在入侵前的常见侦察手段,攻击者会借助自动化工具批量试探服务器上可能存在的敏感路径,例如后台入口、备份文件、配置文件、日志目录等。这类扫描行为会完整记录在Nginx访问日志中,只要掌握正确的分析方法,就能及时发现自己是否正在被扫描,并针对性地加固防护。本文将从日志结构、特征识别、分析工具和防护配置四个层面展开讲解。

一、读懂Nginx访问日志的结构
Nginx默认的combined日志格式包含客户端IP、请求时间、请求方法、URL、状态码、响应大小、Referer和User-Agent等字段。要做扫描识别,先得明确每一列的含义。可以在nginx.conf中查看或自定义日志格式:
log_format scan '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time';
access_log /var/log/nginx/access.log scan;
在这套格式里,$remote_addr代表来源IP,$request是完整的请求行,$status是响应状态码。扫描行为最突出的特征是:单个IP在短时间内产生大量请求,且其中大量请求返回404或403状态码。正常用户的访问路径集中且命中率高,而扫描器会机械地遍历一个预设的字典列表,命中率极低。
另外,User-Agent也是重要的判断依据。不少扫描工具会直接暴露身份,比如sqlmap、dirsearch、gobuster、masscan等关键字;稍微谨慎一点的攻击者会伪装成普通浏览器UA,但伪装往往存在细节破绽,例如缺少合理的Accept-Language头,或者UA字符串与请求行为不匹配。这些都需要结合多个字段综合判断。
二、用命令行快速识别扫描行为
拿到一份访问日志后,第一步是统计高频来源IP。一条简单的awk命令就能把请求量排名前列的IP列出来:
# 统计请求量最多的前20个IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
第二步,把请求量高的IP与404状态码交叉分析。扫描器的典型画像是请求量大且404占比极高,下面的命令统计每个IP的404次数:
awk '$9 == 404 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
第三步,针对目录遍历类攻击,还要专门检索路径穿越载荷。攻击者会尝试在URL中注入../序列或其编码变体,试图跳出Web根目录读取系统文件。可以用grep配合正则来定位:
# 检索路径穿越特征,包括编码变体 grep -iE '(\.\./|%2e%2e%2f|%2e%2e/|\.\.%2f)' /var/log/nginx/access.log # 检索常见敏感路径探测 grep -iE '(wp-admin|\.git|\.env|\.svn|phpmyadmin|backup|\.bak|\.sql)' /var/log/nginx/access.log | head -30
需要提醒的是,只看单一维度容易误判。比如某些监控探针或健康检查也会产生固定频率的请求,CDN回源节点IP的请求量也可能很高。因此建议把时间窗口纳入考量:统计每个IP在每分钟内的请求次数,正常用户几乎不可能一分钟内请求几百个不同的路径。可以借助一个稍复杂的脚本来完成:
#!/bin/bash
# 找出10秒内请求超过50次且404占比过半的IP
awk '{ip=$1; gsub(/\[/,"",$4); split($4,t,":"); key=ip" "t[1]":"t[2]":"t[3];
req[key]++; if($9==404) nf[key]++}
END {for(k in req) if(req[k]>50 && nf[k]/req[k]>0.5) print k, req[k], nf[k]}' \
/var/log/nginx/access.log
这段脚本按分钟粒度聚合每个IP的请求总数与404数量,一旦请求数超过阈值且404占比过半,就基本可以断定是扫描行为,输出结果可以直接作为封禁名单的输入。
三、借助工具做持续化日志分析
命令行适合临时排查,如果要长期监控,需要更系统的方案。GoAccess是一款终端下的实时日志分析工具,安装后直接指向日志文件即可看到按IP、URL、状态码维度的实时统计:
goaccess /var/log/nginx/access.log --log-format=COMBINED -o /var/www/html/report.html --real-time-html
除了GoAccess,还可以把Nginx日志接入ELK体系。通过Filebeat采集日志、Logstash做解析过滤、Kibana做可视化,可以方便地建立诸如"单IP每分钟404次数超过100则告警"这类规则。对于中小规模站点,也可以选择更轻量的方案,比如fail2ban。fail2ban通过正则匹配日志中的恶意特征,自动调用防火墙封禁IP,配置一个针对扫描的过滤规则如下:
# /etc/fail2ban/filter.d/nginx-scan.conf [Definition] failregex = ^<HOST> .* "(GET|POST|HEAD) .*(wp-admin|\.git|\.env|phpmyadmin|\.\./).*" (404|403) ignoreregex = # /etc/fail2ban/jail.local 中启用 [nginx-scan] enabled = true filter = nginx-scan logpath = /var/log/nginx/access.log maxretry = 20 findtime = 60 bantime = 3600
这套配置的含义是:60秒内匹配到20次扫描特征请求的IP,将被封禁3600秒。maxretry和findtime的值需要根据站点实际流量调整,流量大的站点可以适当放宽阈值,避免误伤正常用户。
四、从Nginx配置层面加固防护
识别只是第一步,更主动的做法是让扫描从一开始就难以得逞。首先是关闭目录索引,避免攻击者直接浏览目录内容:
location /logs/ {
autoindex off;
deny all; # 日志目录严禁对外暴露
return 403;
}
其次是阻断典型的路径穿越与敏感后缀访问。利用Nginx的location匹配,可以把明显恶意的请求直接拒绝,同时记录到单独的错误日志便于审计:
location ~* /\.{|(\.\./)|\.env$|\.git$|\.bak$|\.sql$|\.svn$ {
deny all;
access_log /var/log/nginx/attack.log scan;
return 403;
}
第三是限制请求速率。Nginx自带的limit_req模块可以对单个IP的请求频率做硬性约束,超出速率的请求直接返回503,扫描器的效率会被大幅压制:
http {
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
server {
limit_req zone=perip burst=20 nodelay;
limit_req_status 503;
}
}
其中rate=10r/s表示平均每秒最多10个请求,burst=20允许瞬时的突发流量。对普通用户来说这个限制几乎无感,但对一秒发几十上百个请求的扫描器来说就是实质性的阻断。另外别忘了隐藏版本信息,将server_tokens设为off,减少暴露给攻击者的指纹信息。对于需要更强防护的场景,还可以在Nginx前面加一层ModSecurity或接入云WAF,用规则库拦截更复杂的攻击载荷。
五、建立检测与响应的闭环
安全不是一次性配置,而是持续运营的过程。建议的做法是:每天定时跑一次日志分析脚本,把结果汇总到运维群或邮件;对fail2ban的封禁记录定期复盘,检查是否有误封;关注新增的扫描特征,及时更新过滤正则。日志本身也要做好管理,配置logrotate按天切割并压缩归档,保留足够长的周期供事后溯源。
最后强调一点,日志目录自身千万不要放在Web可访问路径下。历史上大量真实入侵案例的起点,就是管理员把access.log放在站点根目录里,攻击者通过直接访问日志文件,从中获得了后台路径、内网地址甚至Cookie等敏感信息。把检测、拦截、审计三件事串起来,Nginx站点的安全防护才算真正落地。