Nginx拦截非法请求时必须记录完整原始信息,包括$request、$args等关键字段;默认return 403不记全量请求,需自定义log_format并单独配置access_log;应避免if+return,优先在location中结合map或ModSecurity实现可靠拦截与日志记录。

当 Nginx 拦截含非法字符的请求(如 ../、union select、<script></script>、单引号、分号等)时,日志是否记录、如何记录、记录哪些字段,直接关系到能否回溯攻击行为、支撑取证分析和满足合规要求。关键不是“拦住就行”,而是“拦得清、记得全、查得准”。
日志必须记录被拦截请求的原始信息
默认情况下,Nginx 使用 return 403 或 deny 拦截请求时,不会自动记录完整请求内容到 access_log——它只记录基础字段(如 IP、时间、状态码),而缺失最关键的 $request_uri 或 $args,导致无法还原攻击载荷。
- 务必在自定义日志格式中显式包含:
$request(含方法+URI+协议)、$args(查询参数)、$http_user_agent、$http_x_forwarded_for(防代理隐藏) - 示例安全日志格式(推荐加入 nginx.conf 的 http 块):
log_format security '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$args"'; - 对拦截规则所在 server 或 location 块,单独指定该格式:
access_log /var/log/nginx/security.log security;
区分“真实拦截”与“日志伪造”的记录方式
仅靠 if + return 403 有缺陷:它不触发常规 access_log 流程,且易被绕过(如大小写变形、URL 编码)。更可靠的做法是结合 map + log_by_lua* 或使用 ModSecurity,但若用原生配置,需确保:
- 拦截逻辑放在
location块内(而非 if),并配合access_log显式启用; - 避免在
if中使用return后再写access_log,Nginx 不保证执行顺序; - 对高危路径(如
/wp-login.php、/api/auth)单独设置日志格式,强制记录全部请求头($http_*)与响应体长度($bytes_sent); - 开启
error_log的 debug 级别(临时)可验证拦截是否生效,但生产环境应设为warn或error防止日志爆炸。
审计时重点比对三类日志字段组合
单看一条 403 日志意义有限,安全审计需建立关联视图:
-
IP + 时间窗口 + 状态码分布:统计某 IP 在 5 分钟内产生 ≥10 条 403,且含多个不同恶意模式(如同时出现
../和select),基本可判定为自动化扫描; -
Request URI + Args + User-Agent:同一 UA(如
sqlmap/2.0)反复请求/search?q=1' OR '1'='1,即构成 SQLi 尝试证据链; -
X-Forwarded-For + Real IP:若
$remote_addr是 CDN 或反向代理 IP,而$http_x_forwarded_for存在多层逗号分隔值,需提取最左非私有地址作为溯源依据,并检查该字段是否可被客户端伪造(应在入口层清洗)。
自动化归档与告警的落地要点
人工翻日志不可持续,需将规范转化为可执行机制:
- 用
logrotate每日切分security.log,保留 90 天,压缩存档; - 通过
awk '/"GET.*\.php\?.*union.*select/ {print $1,$4,$9}' /var/log/nginx/security.log快速提取高危样本; - 接入 ELK 或 Grafana Loki,对
status:403日志添加标签attack_type:sql_injection、attack_type:xss,便于聚合与看板呈现; - 与 Fail2Ban 联动:匹配日志中连续 5 次含
../的 403 请求,自动封禁对应$remote_addr24 小时。


















