云平台服务器遭遇黑客攻击?用 Nginx 批量封锁敏感接口,自动返回 444 关闭连接

前言

最近在处理云平台服务器安全问题时,发现大量来自外部的扫描请求,目标直指 /actuator/swagger-ui/druid/health/metrics 等敏感路径。这些路径一旦暴露,黑客可能获取系统内部信息、配置详情甚至数据库监控页面,为进一步渗透提供便利。

本文将介绍一种通用的 Nginx 加固方案:批量在所有 server 块中插入安全规则,让外部访问这些敏感路径时直接返回 444(Nginx 特有的"关闭连接"响应码),从源头阻断扫描,降低被攻击风险。

方案适用于所有使用标准 Nginx 的 Linux 云服务器,不依赖特定云厂商,只需本地修改 Nginx 配置,风险可控。


为什么要这样做?

黑客在对云服务器发起攻击前,通常会进行自动化扫描,探测常见的管理端点、API 文档、监控页面等。例如:

  • /actuator:Spring Boot 应用监控端点,可能泄露 beans、env、health、metrics 等信息。
  • /swagger-ui/v2/api-docs:接口文档,暴露 API 结构和参数。
  • /druid:阿里巴巴数据库连接池监控页面,若未授权可直接查看 SQL 执行情况。
  • /health/metrics:应用健康检查和指标信息,可能泄露内部状态。

这些路径如果直接返回 404 或 403,虽然也能拦截,但攻击者仍可判断服务器存在,并继续尝试其他路径。而 444 是 Nginx 自定义的非标准响应码,表示直接关闭连接,不返回任何 HTTP 响应,可以让扫描器认为目标不可达或异常,从而增加攻击难度,隐藏真实服务信息。


这样做的作用

  1. 批量防护 :自动扫描 /etc/nginx/conf.d/ 下所有 .conf 文件,为每个 server 块插入统一的敏感路径封锁规则,避免遗漏。
  2. 精确匹配 :规则使用正则匹配,覆盖 /actuator/api/actuator/swagger-ui 及其子路径,防止绕过。
  3. 安全可控 :脚本执行前自动备份配置,并在修改后运行 nginx -t 检查语法;若语法错误自动回滚,不影响线上服务。
  4. 无业务侵入 :只拦截指定的敏感路径,正常业务接口不受影响。同时脚本会跳过 upstream 块内的 server 指令,不会误伤后端服务定义。

适用场景与前提

  • 服务器使用标准 Nginx 作为反向代理或 Web 服务器。
  • 配置文件位于 /etc/nginx/conf.d/ 目录下(可包含子目录)。
  • 确认没有内部系统依赖这些敏感路径(例如监控探针、健康检查)。
  • 云负载均衡器的健康检查使用 TCP 协议,不受 HTTP 444 影响。

完整操作步骤

第 1 步:备份现有 Nginx 配置

bash 复制代码
cp -r /etc/nginx/conf.d /etc/nginx/conf.d.bak.$(date +%Y%m%d_%H%M%S)

执行后会生成类似 /etc/nginx/conf.d.bak.20260821_173000 的备份目录。

验证备份:

bash 复制代码
ls -ld /etc/nginx/conf.d.bak.*

第 2 步:创建敏感接口封锁规则文件

bash 复制代码
mkdir -p /etc/nginx/snippets
vim /etc/nginx/snippets/block_sensitive_paths.conf

i 进入插入模式,粘贴以下内容:

nginx 复制代码
# 敏感接口统一封锁规则(修正正则,匹配子路径)
location ~* ^/actuator(/.*)?$ { access_log off; return 444; }
location ~* ^/api/actuator(/.*)?$ { access_log off; return 444; }
location ~* ^/(swagger-resources|v2/api-docs|v3/api-docs|swagger-ui|webjars/springfox)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(swagger-resources|v2/api-docs|v3/api-docs|swagger-ui|webjars/springfox)(/.*)?$ { access_log off; return 444; }
location ~* ^/(swagger-ui\.html|doc\.html)$ { access_log off; return 444; }
location ~* ^/api/(swagger-ui\.html|doc\.html)$ { access_log off; return 444; }
location ~* ^/(druid|h2-console|jolokia)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(druid|h2-console|jolokia)(/.*)?$ { access_log off; return 444; }
location ~* ^/(health|env|beans|configprops|mappings|metrics|heapdump|threaddump|logfile|loggers|shutdown|trace|autoconfig|dump)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(health|env|beans|configprops|mappings|metrics|heapdump|threaddump|logfile|loggers|shutdown|trace|autoconfig|dump)(/.*)?$ { access_log off; return 444; }

Esc,输入 :wq 保存退出。


第 3 步:创建批量插入脚本

bash 复制代码
vim /root/apply_block_rules.py

i 进入插入模式,粘贴以下完整 Python 脚本:

python 复制代码
#!/usr/bin/env python3
import os
import re
import subprocess
import shutil
import sys
from datetime import datetime

CONF_ROOT = "/etc/nginx/conf.d"
SNIPPET_DIR = "/etc/nginx/snippets"
SNIPPET_FILE = os.path.join(SNIPPET_DIR, "block_sensitive_paths.conf")
BACKUP_DIR = f"/etc/nginx/conf.d.bak.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
INCLUDE_LINE = f"    include {SNIPPET_FILE};\n"

def backup_configs():
    print(f"[1/5] 备份配置到 {BACKUP_DIR}")
    shutil.copytree(CONF_ROOT, BACKUP_DIR)
    print("备份完成。")

def create_snippet():
    print("[2/5] 创建规则文件...")
    os.makedirs(SNIPPET_DIR, exist_ok=True)
    content = """# 敏感接口统一封锁规则(修正正则,匹配子路径)
location ~* ^/actuator(/.*)?$ { access_log off; return 444; }
location ~* ^/api/actuator(/.*)?$ { access_log off; return 444; }
location ~* ^/(swagger-resources|v2/api-docs|v3/api-docs|swagger-ui|webjars/springfox)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(swagger-resources|v2/api-docs|v3/api-docs|swagger-ui|webjars/springfox)(/.*)?$ { access_log off; return 444; }
location ~* ^/(swagger-ui\\.html|doc\\.html)$ { access_log off; return 444; }
location ~* ^/api/(swagger-ui\\.html|doc\\.html)$ { access_log off; return 444; }
location ~* ^/(druid|h2-console|jolokia)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(druid|h2-console|jolokia)(/.*)?$ { access_log off; return 444; }
location ~* ^/(health|env|beans|configprops|mappings|metrics|heapdump|threaddump|logfile|loggers|shutdown|trace|autoconfig|dump)(/.*)?$ { access_log off; return 444; }
location ~* ^/api/(health|env|beans|configprops|mappings|metrics|heapdump|threaddump|logfile|loggers|shutdown|trace|autoconfig|dump)(/.*)?$ { access_log off; return 444; }
"""
    with open(SNIPPET_FILE, 'w') as f:
        f.write(content)
    print(f"规则文件已创建: {SNIPPET_FILE}")

def get_conf_files():
    files = []
    for root, dirs, names in os.walk(CONF_ROOT):
        for name in names:
            if not name.endswith(".conf"):
                continue
            path = os.path.join(root, name)
            if ".bak" in path or "/template/" in path:
                continue
            files.append(path)
    return files

def is_server_block_start(lines, i):
    """判断第 i 行是否是一个真正的 server 块起始(排除 upstream 块内 server 指令)"""
    line = lines[i]
    stripped = line.strip()
    if stripped.startswith('#'):
        return False

    # 形式1:server {  (同一行)
    if re.match(r'^\s*server\s*\{', line):
        return True

    # 形式2:纯 server 行,后换行 {
    if re.match(r'^\s*server\s*$', line):
        # 向后找到第一个非注释行,检查是否包含 {
        k = i + 1
        while k < len(lines):
            s = lines[k].strip()
            if s.startswith('#'):
                k += 1
                continue
            if '{' in lines[k]:
                return True
            else:
                return False
        return False

    return False

def insert_include_in_file(file_path):
    with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
        lines = f.readlines()

    full = "".join(lines)
    if SNIPPET_FILE in full:
        return False, 0

    new_lines = []
    i = 0
    modified = False
    insert_count = 0

    while i < len(lines):
        line = lines[i]

        if is_server_block_start(lines, i):
            # 找到 server 块的 { 所在行,插入 include
            # 如果是 server { 同在一行,则直接在该行后插入
            # 如果是纯 server 行,{ 在后续行,则在找到 { 的那一行后插入
            j = i
            found_brace = False
            while j < len(lines):
                s = lines[j].strip()
                if s.startswith('#'):
                    j += 1
                    continue
                if '{' in lines[j]:
                    found_brace = True
                    break
                # 不应该发生,安全起见中断
                if j > i + 5:  # 最多向后找5行
                    break
                j += 1

            if found_brace:
                # 把从 i 到 j 的行原样加入
                for k in range(i, j+1):
                    new_lines.append(lines[k])
                # 在 j 行后插入 include
                new_lines.append(INCLUDE_LINE)
                insert_count += 1
                i = j + 1
                modified = True
            else:
                new_lines.append(line)
                i += 1
        else:
            new_lines.append(line)
            i += 1

    if modified:
        with open(file_path, 'w', encoding='utf-8') as f:
            f.writelines(new_lines)
    return modified, insert_count

def apply_rules():
    print("[3/5] 批量插入 include ...")
    files = get_conf_files()
    modified_files = 0
    total_inserts = 0
    for fp in files:
        mod, cnt = insert_include_in_file(fp)
        if mod:
            modified_files += 1
            total_inserts += cnt
            print(f"  [+] 修改: {fp} (插入 {cnt} 个 include)")
    print(f"处理完成:修改 {modified_files} 个文件,共插入 {total_inserts} 个 include。")

def test_nginx():
    print("[4/5] 检查 Nginx 语法...")
    result = subprocess.run(["nginx", "-t"], capture_output=True, text=True)
    print(result.stdout)
    print(result.stderr)
    return result.returncode == 0

def reload_nginx():
    print("[5/5] 重载 Nginx ...")
    subprocess.run(["systemctl", "reload", "nginx"], check=True)
    print("部署成功!")

def verify():
    print("\n=== 验证引用情况 ===")
    files = get_conf_files()
    for fp in files:
        with open(fp, 'r') as f:
            content = f.read()
        count = content.count(SNIPPET_FILE)
        if count > 0:
            print(f"{fp}: {count} 个 include")
    print("验证完成。")

def rollback():
    print("检测到语法错误,正在自动回滚...")
    if os.path.exists(CONF_ROOT):
        shutil.rmtree(CONF_ROOT)
    shutil.copytree(BACKUP_DIR, CONF_ROOT)
    print(f"已恢复配置,备份目录保留: {BACKUP_DIR}")
    print("正在重新加载 Nginx 使回滚生效...")
    subprocess.run(["systemctl", "reload", "nginx"], check=False)

def main():
    backup_configs()
    create_snippet()
    apply_rules()
    if test_nginx():
        reload_nginx()
        verify()
        print("✅ 敏感路径封锁规则已生效。")
    else:
        rollback()
        print("❌ 语法错误,已回滚并重载 Nginx。请检查配置。")
        sys.exit(1)

if __name__ == "__main__":
    main()

Esc,输入 :wq 保存退出。


第 4 步:执行脚本

bash 复制代码
chmod +x /root/apply_block_rules.py
python3 /root/apply_block_rules.py

脚本会自动完成:

  1. 备份配置
  2. 创建规则文件
  3. 批量插入 include(准确识别 server {} 块,忽略 upstreamserver 指令、注释)
  4. 检查 Nginx 语法
  5. 语法通过则重载,失败则自动回滚并重载旧配置
  6. 输出验证信息

第 5 步:手动验证(可选,但建议)

5.1 查看哪些文件包含规则引用
bash 复制代码
grep -rl "block_sensitive_paths.conf" /etc/nginx/conf.d/
5.2 查看每个文件的 include 数量
bash 复制代码
grep -rc "block_sensitive_paths.conf" /etc/nginx/conf.d/ | grep -v ':0'
5.3 测试敏感路径拦截
bash 复制代码
curl -I http://127.0.0.1/actuator
curl -I http://127.0.0.1/actuator/health

如果返回 HTTP/1.1 444 或 curl 报连接被关闭,说明拦截成功。


回退步骤

如果脚本未自动回滚或需要手动恢复,可执行以下操作:

  1. 查看备份目录
bash 复制代码
ls -td /etc/nginx/conf.d.bak.* | head -n 5
  1. 恢复备份(替换为实际备份目录名)
bash 复制代码
rm -rf /etc/nginx/conf.d
cp -r /etc/nginx/conf.d.bak.20260821_173000 /etc/nginx/conf.d
  1. 检查并重载
bash 复制代码
nginx -t && systemctl reload nginx

总结

通过以上步骤,我们可以在所有 Nginx server 块中统一加入敏感路径封锁规则,让外部访问 /actuator/swagger-ui/druid/health/metrics 等路径时直接被关闭连接(444),有效隐藏敏感信息,降低被扫描攻击的风险。

整个方案具备以下特点:

  • 自动化:脚本自动备份、自动插入、自动检查、自动回滚。
  • 安全性:只修改 Nginx 配置,不涉及应用代码;语法检查失败自动恢复。
  • 通用性:适用于任何使用标准 Nginx 的 Linux 云服务器。
  • 无侵入 :不影响正常业务,不干扰 upstream 定义。

建议先在测试环境验证一遍,再上生产环境执行,确保万无一失。

效果

相关推荐
qetfw1 小时前
Debian OpenLDAP 目录服务配置:DN、LDIF、ldapsearch 与认证验证
linux·运维·debian
guwentian1 小时前
从0到1手写 AI Agent Harness:为什么护城河不在模型,而在工程外壳
人工智能·python·安全·deepseek·harness
wzq11_6661 小时前
云计算运维学习day22——Ansible-Roles
运维·学习·云计算
一只旭宝1 小时前
预约系统版本2(基于第一版改良)
服务器·数据库·c++
HiDev_1 小时前
【非标自动化】2、认识元器件(数字量输入输出模块)
运维·自动化
桐桐桐2 小时前
Python 实战:批量生成带来源参数的 WhatsApp 短链 + 二维码
服务器·数据库·python·前端框架·ip·跨境电商·独立站
qq_349447952 小时前
在ollama下部署DeepSeek-r1模型(linux机器)
linux·运维·服务器
白猫不黑2 小时前
AI Agent自动化渗透测试实战:从原理到红队实践
运维·人工智能·web安全·网络安全·信息安全·渗透测试·自动化
何以解忧,唯有..2 小时前
HTTP 与 HTTPS:从明文传输到安全加密的演进
安全·http·https