生产级 Nginx 安全基线加固:5 步阻断 90% 恶意探测与扫描器

2026-10-10 5点热度 0人点赞 0条评论

0x00 前言与威胁模型

在公网暴露的任何 Web 入口,都会在几分钟内被来自全球的自动化扫描器(如 Nuclei、Nikto、Sqlmap、Gobuster 等)常态化轮询。默认配置下的 Nginx 存在以下典型隐患:

  1. 指纹完全泄露:Server: nginx/1.24.x 响应头直接暴露出微版本号,攻击者可针对性检索 CVE 进行精准打靶。

  2. 敏感工程资产外溢:CI/CD 部署遗漏或误解压导致的 .git、.env、备份文件(.sql、.tar.gz、.bak)直接暴露,容易引发源码及数据库凭据泄露。

  3. 恶意扫描侵蚀后端:大并发扫描和暴力破解不仅耗尽 PHP-FPM / upstream 资源池,还会造成日志膨胀与带宽资源浪费。

虽然 Nginx 原生规则不能完全替代商用或开源 WAF(如 ModSecurity / Coraza / 商业云 WAF),但以不到 40 行的零侵入配置,封死最低成本的攻击面,是性价比极高的工程化实践。

0x01 变更前:30 秒现状巡检(Ops Healthcheck)

在部署变更前,建议先在终端执行以下检查,确认当前节点的暴露状态:

1. 检查版本指纹暴露

Bash

curl -sI https://your-domain.com | grep -i '^server:'
  • 风险表现:返回 Server: nginx/1.24.0(包含小版本号)。

  • 预期安全表现:返回 Server: nginx 或自定义混淆字符。

2. 探针文件扫描探测

Bash

for p in /.git/config /.env /db.sql /backup.zip /dump.sql; do
  printf "%-16s -> HTTP %s\n" "$p" "$(curl -s -o /dev/null -w '%{http_code}' https://your-domain.com$p)"
done
  • 风险表现:返回 200(高危直接下载)或 403(说明文件真实存在但被权限拦截,同样是信息泄露)。

  • 安全表现:均返回统一的 404 或规则主动阻断的 403。

3. 查看近期访问日志中的恶意 UA 痕迹

Bash

grep -iE 'sqlmap|nikto|nmap|acunetix|nessus|nuclei|gobuster|dirbuster|wpscan|masscan' \
  /var/log/nginx/access.log | awk '{print $1, $12}' | sort | uniq -c | sort -rn | head -10

0x02 推荐架构:模块化 Snippets 目录设计

为了保证多 vhost / server 配置的一致性与可维护性,强烈建议不要将所有规则全部硬编码在单文件里,推荐将其抽离为通用片段:

Plaintext

/etc/nginx/
├── nginx.conf
└── conf.d/
    └── snippets/
        ├── security_core.conf      # 注入 http {}
        └── security_server.conf    # 注入 server {}

0x03 核心加固配置实施

片段一:全局基础与上下文(注入 http {} 块)

新建 /etc/nginx/conf.d/snippets/security_core.conf:

Nginx

# 1. 隐藏 Nginx 详细版本信息与错误页版本号
server_tokens off;

# 2. 恶意 User-Agent 映射字典(利用高效哈希表匹配,O(1) 效率优于纯正则匹配)
map $http_user_agent $is_malicious_scanner {
    default 0;
    "~*(sqlmap|nikto|nmap|masscan|acunetix|nessus|nuclei|xray|wpscan|gobuster|dirbuster|zgrab|curl/.*python)" 1;
}

# 3. 流量控制 Zone(基于二进制远程 IP 内存表,降低内存占用)
# 全局请求速率:每秒 10 个请求,10MB 内存空间约可容纳 16 万 IP 状态
limit_req_zone $binary_remote_addr zone=global_req_limit:10m rate=10r/s;

# 核心认证接口更严格的限流区(如登录、发信接口)
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=1r/s;

# IP 连接数上限限制
limit_conn_zone $binary_remote_addr zone=ip_conn_limit:10m;

片段二:虚拟主机级防御拦截(注入 server {} 块)

新建 /etc/nginx/conf.d/snippets/security_server.conf:

Nginx

# -------------------------------------------------------------
# 1. 关键例外放行:Let's Encrypt / ACME 挑战放行
# 必须使用 ^~ 前缀匹配,抢在后续正则 location 之前生效,避免续签被误伤阻断
# -------------------------------------------------------------
location ^~ /.well-known/acme-challenge/ {
    allow all;
    access_log off;
}

# -------------------------------------------------------------
# 2. 敏感隐藏目录与工程配置文件阻断 (.git, .env, .svn 等)
# -------------------------------------------------------------
location ~* /\.(git|svn|hg|env|DS_Store) {
    deny all;
    access_log off;
    log_not_found off;
}

# -------------------------------------------------------------
# 3. 备份、脚本、数据库转储后缀阻断
# -------------------------------------------------------------
location ~* \.(bak|old|orig|sql|zip|rar|tar|gz|log|ini|sh|swp|dump)$ {
    deny all;
    access_log off;
    log_not_found off;
}

# -------------------------------------------------------------
# 4. 扫描器 User-Agent 与异常 HTTP 请求方法阻断
# -------------------------------------------------------------
# 阻断恶意扫描器
if ($is_malicious_scanner) {
    return 403;
}

# 仅放行生产标准 HTTP 请求方法(按需开放 PUT/DELETE)
if ($request_method !~ ^(GET|HEAD|POST)$) {
    return 405;
}

# -------------------------------------------------------------
# 5. 限速与并发限制配置
# -------------------------------------------------------------
limit_req zone=global_req_limit burst=20 nodelay;
limit_conn ip_conn_limit 20;
limit_req_status 429;
limit_conn_status 429;

# -------------------------------------------------------------
# 6. HTTP 安全防护响应头 (Security Headers)
# -------------------------------------------------------------
# 点击劫持防御
add_header X-Frame-Options "SAMEORIGIN" always;

# 禁止 MIME 类型嗅探
add_header X-Content-Type-Options "nosniff" always;

# 规避敏感参数随 Referer 外泄
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# 仅 HTTPS 站点启用:强制 1 年 HSTS,包含子域名
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

0x04 生产环境“大坑”排错指南(SRE Gotchas)

在生产推广这套配置时,需要特别警惕以下三个运维踩坑点:

问题现象 根因剖析 运维解决对策
add_header 丢失失效 Nginx 原生继承机制缺陷:如果子 location(如 location /api 或 location ~ \.php$)内部声明了任何 add_header,父级 server 块的所有 add_header 将被彻底丢弃覆盖。 将全局安全头单独保存为 security_headers.conf,在包含额外 Header 的子 location 内执行 include snippets/security_headers.conf;。
Let's Encrypt 证书续签失败 Certbot 挑战路径为 /.well-known/acme-challenge/...,命中以 . 开头的正则黑名单直接返回 403。 确保 location ^~ /.well-known/acme-challenge/ 处于正则规则之前,^~ 修饰符会强制停止后续正则匹配。
静态资源或前端 SPA 报 429 并发资源较多时,单个页面请求 30+ 静态资源瞬间耗尽 burst=20 队列,导致页面图片或 CSS 破损。 对 `location ~* .(js

0x05 发布上线与校验流程

上线前遵循严格的语法检查 -> 热重载 -> 外部黑盒回归流程:

Bash

# Step 1: 必须通过配置语法自检
sudo nginx -t

# Step 2: 平滑重载(不中断现有活跃连接)
sudo nginx -s reload

黑盒自动化验证脚本

复制并在控制台执行以下单行回归测试:

Bash

DOMAIN="your-domain.com"

echo "=== 1. 验证版本指纹 ==="
curl -sI "https://$DOMAIN" | grep -i '^server:'

echo "=== 2. 验证敏感路径拦截 (预期 403) ==="
curl -sI "https://$DOMAIN/.git/config" | head -n 1
curl -sI "https://$DOMAIN/database.sql" | head -n 1

echo "=== 3. 验证恶意扫描器拦截 (预期 403) ==="
curl -sI -A "sqlmap/1.7" "https://$DOMAIN" | head -n 1

echo "=== 4. 验证异常 HTTP 请求方法 (预期 405) ==="
curl -sI -X TRACE "https://$DOMAIN" | head -n 1

echo "=== 5. 验证安全响应头 ==="
curl -sI "https://$DOMAIN" | grep -iE 'x-frame|x-content-type|referrer|strict-transport'

0x06 生产发布检查清单(Release Checklist)

  • [ ] nginx -t 测试输出 syntax is ok 与 test is successful。

  • [ ] 站点响应头中的 Server 字段仅显示 nginx,无版本号。

  • [ ] 探针路径 /.git/、.env、*.sql 均能被有效阻断。

  • [ ] 伪装扫描器 UA(如 sqlmap)访问能准确返回 403 Forbidden。

  • [ ] 非标准请求方法返回 405 Method Not Allowed。

  • [ ] 证书签发路径 /.well-known/acme-challenge/ 访问未被误拦。

  • [ ] 静态资源密集页面(包含多张图片与 CSS)加载顺畅,无误伤的 429 Too Many Requests。

admin

这个人很懒,什么都没留下

文章评论

您需要 登录 之后才可以评论