前言
最近在处理云平台服务器安全问题时,发现大量来自外部的扫描请求,目标直指/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 响应,可以让扫描器认为目标不可达或异常,从而增加攻击难度,隐藏真实服务信息。
这样做的作用
- 批量防护:自动扫描
/etc/nginx/conf.d/下所有.conf文件,为每个server块插入统一的敏感路径封锁规则,避免遗漏。 - 精确匹配:规则使用正则匹配,覆盖
/actuator、/api/actuator、/swagger-ui及其子路径,防止绕过。 - 安全可控:脚本执行前自动备份配置,并在修改后运行
nginx -t检查语法;若语法错误自动回滚,不影响线上服务。 - 无业务侵入:只拦截指定的敏感路径,正常业务接口不受影响。同时脚本会跳过
upstream块内的server指令,不会误伤后端服务定义。
适用场景与前提
- 服务器使用标准 Nginx 作为反向代理或 Web 服务器。
- 配置文件位于
/etc/nginx/conf.d/目录下(可包含子目录)。 - 确认没有内部系统依赖这些敏感路径(例如监控探针、健康检查)。
- 云负载均衡器的健康检查使用 TCP 协议,不受 HTTP 444 影响。
完整操作步骤
第 1 步:备份现有 Nginx 配置
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的备份目录。
验证备份:
ls-ld/etc/nginx/conf.d.bak.*第 2 步:创建敏感接口封锁规则文件
mkdir-p/etc/nginx/snippetsvim/etc/nginx/snippets/block_sensitive_paths.conf按i进入插入模式,粘贴以下内容:
# 敏感接口统一封锁规则(修正正则,匹配子路径) 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 步:创建批量插入脚本
vim/root/apply_block_rules.py按i进入插入模式,粘贴以下完整 Python 脚本:
#!/usr/bin/env python3importosimportreimportsubprocessimportshutilimportsysfromdatetimeimportdatetime 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"defbackup_configs():print(f"[1/5] 备份配置到{BACKUP_DIR}")shutil.copytree(CONF_ROOT,BACKUP_DIR)print("备份完成。")defcreate_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; } """withopen(SNIPPET_FILE,'w')asf:f.write(content)print(f"规则文件已创建:{SNIPPET_FILE}")defget_conf_files():files=[]forroot,dirs,namesinos.walk(CONF_ROOT):fornameinnames:ifnotname.endswith(".conf"):continuepath=os.path.join(root,name)if".bak"inpathor"/template/"inpath:continuefiles.append(path)returnfilesdefis_server_block_start(lines,i):"""判断第 i 行是否是一个真正的 server 块起始(排除 upstream 块内 server 指令)"""line=lines[i]stripped=line.strip()ifstripped.startswith('#'):returnFalse# 形式1:server { (同一行)ifre.match(r'^\s*server\s*\{',line):returnTrue# 形式2:纯 server 行,后换行 {ifre.match(r'^\s*server\s*$',line):# 向后找到第一个非注释行,检查是否包含 {k=i+1whilek<len(lines):s=lines[k].strip()ifs.startswith('#'):k+=1continueif'{'inlines[k]:returnTrueelse:returnFalsereturnFalsereturnFalsedefinsert_include_in_file(file_path):withopen(file_path,'r',encoding='utf-8',errors='ignore')asf:lines=f.readlines()full="".join(lines)ifSNIPPET_FILEinfull:returnFalse,0new_lines=[]i=0modified=Falseinsert_count=0whilei<len(lines):line=lines[i]ifis_server_block_start(lines,i):# 找到 server 块的 { 所在行,插入 include# 如果是 server { 同在一行,则直接在该行后插入# 如果是纯 server 行,{ 在后续行,则在找到 { 的那一行后插入j=i found_brace=Falsewhilej<len(lines):s=lines[j].strip()ifs.startswith('#'):j+=1continueif'{'inlines[j]:found_brace=Truebreak# 不应该发生,安全起见中断ifj>i+5:# 最多向后找5行breakj+=1iffound_brace:# 把从 i 到 j 的行原样加入forkinrange(i,j+1):new_lines.append(lines[k])# 在 j 行后插入 includenew_lines.append(INCLUDE_LINE)insert_count+=1i=j+1modified=Trueelse:new_lines.append(line)i+=1else:new_lines.append(line)i+=1ifmodified:withopen(file_path,'w',encoding='utf-8')asf:f.writelines(new_lines)returnmodified,insert_countdefapply_rules():print("[3/5] 批量插入 include ...")files=get_conf_files()modified_files=0total_inserts=0forfpinfiles:mod,cnt=insert_include_in_file(fp)ifmod:modified_files+=1total_inserts+=cntprint(f" [+] 修改:{fp}(插入{cnt}个 include)")print(f"处理完成:修改{modified_files}个文件,共插入{total_inserts}个 include。")deftest_nginx():print("[4/5] 检查 Nginx 语法...")result=subprocess.run(["nginx","-t"],capture_output=True,text=True)print(result.stdout)print(result.stderr)returnresult.returncode==0defreload_nginx():print("[5/5] 重载 Nginx ...")subprocess.run(["systemctl","reload","nginx"],check=True)print("部署成功!")defverify():print("\n=== 验证引用情况 ===")files=get_conf_files()forfpinfiles:withopen(fp,'r')asf:content=f.read()count=content.count(SNIPPET_FILE)ifcount>0:print(f"{fp}:{count}个 include")print("验证完成。")defrollback():print("检测到语法错误,正在自动回滚...")ifos.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)defmain():backup_configs()create_snippet()apply_rules()iftest_nginx():reload_nginx()verify()print("✅ 敏感路径封锁规则已生效。")else:rollback()print("❌ 语法错误,已回滚并重载 Nginx。请检查配置。")sys.exit(1)if__name__=="__main__":main()按Esc,输入:wq保存退出。
第 4 步:执行脚本
chmod+x /root/apply_block_rules.py python3 /root/apply_block_rules.py脚本会自动完成:
- 备份配置
- 创建规则文件
- 批量插入 include(准确识别
server {}块,忽略upstream内server指令、注释) - 检查 Nginx 语法
- 语法通过则重载,失败则自动回滚并重载旧配置
- 输出验证信息
第 5 步:手动验证(可选,但建议)
5.1 查看哪些文件包含规则引用
grep-rl"block_sensitive_paths.conf"/etc/nginx/conf.d/5.2 查看每个文件的 include 数量
grep-rc"block_sensitive_paths.conf"/etc/nginx/conf.d/|grep-v':0'5.3 测试敏感路径拦截
curl-Ihttp://127.0.0.1/actuatorcurl-Ihttp://127.0.0.1/actuator/health如果返回HTTP/1.1 444或 curl 报连接被关闭,说明拦截成功。
回退步骤
如果脚本未自动回滚或需要手动恢复,可执行以下操作:
- 查看备份目录
ls-td/etc/nginx/conf.d.bak.*|head-n5- 恢复备份(替换为实际备份目录名)
rm-rf/etc/nginx/conf.dcp-r/etc/nginx/conf.d.bak.20260821_173000 /etc/nginx/conf.d- 检查并重载
nginx-t&&systemctl reload nginx总结
通过以上步骤,我们可以在所有 Nginxserver块中统一加入敏感路径封锁规则,让外部访问/actuator、/swagger-ui、/druid、/health、/metrics等路径时直接被关闭连接(444),有效隐藏敏感信息,降低被扫描攻击的风险。
整个方案具备以下特点:
- 自动化:脚本自动备份、自动插入、自动检查、自动回滚。
- 安全性:只修改 Nginx 配置,不涉及应用代码;语法检查失败自动恢复。
- 通用性:适用于任何使用标准 Nginx 的 Linux 云服务器。
- 无侵入:不影响正常业务,不干扰
upstream定义。
建议先在测试环境验证一遍,再上生产环境执行,确保万无一失。