
1. 项目概述为什么Host头攻击不容忽视最近在排查一个线上服务的安全告警时发现日志里频繁出现一些奇怪的请求它们的Host头字段五花八门有的是一个IP地址有的是一个完全不相关的域名甚至还有直接是localhost的。这立刻引起了我的警觉因为这就是典型的Host头攻击也叫Host头注入的试探行为。如果你也使用Nginx作为Web服务器或反向代理那么今天这个“5分钟防御配置”就是你必须要做的安全加固项。这绝不是危言耸听一个配置不当的Host头可能成为攻击者进行密码重置毒化、缓存投毒、绕过访问控制甚至发起SSRF服务器端请求伪造攻击的跳板。简单来说Host头攻击就是攻击者篡改HTTP请求中的Host头部试图欺骗你的Web应用让它误以为请求来自一个合法的、受信任的域名。你的应用如果盲目信任这个头就可能把重置密码的链接发到攻击者的邮箱或者把敏感数据渲染到攻击者控制的页面上。对于Nginx而言即使它自身相对健壮但作为流量的入口它有责任在第一道防线就过滤掉这些非法请求将问题终结在反向代理层而不是传递给后端的应用服务器如Tomcat, Node.js, PHP-FPM等。接下来我将手把手带你完成配置并附上我打磨过的正则表达式模板让你能快速应对各种复杂场景。2. 核心原理与风险场景拆解在动手改配置之前我们必须搞清楚攻击者到底想干什么以及我们的防御措施是如何起作用的。这能帮助你在未来遇到类似问题时举一反三。2.1 Host头的作用与攻击向量HTTP协议中的Host头部主要是在一个IP地址托管多个网站虚拟主机时用来指明客户端想要访问的是哪个特定的域名。Nginx、Apache等Web服务器依赖这个头来决定将请求交给哪个server块来处理。攻击者正是利用了这一点。假设你的服务器IP是203.0.113.10绑定了域名www.example.com。一个正常的请求头是这样的GET / HTTP/1.1 Host: www.example.com ...而攻击者可以轻易地篡改它GET / HTTP/1.1 Host: evil.com ...或者更隐蔽地GET / HTTP/1.1 Host: www.example.com.evil.com如果Nginx配置不当它可能会错误地路由请求如果Nginx配置了基于域名的默认服务器且没有校验这个请求可能被错误地处理。将篡改的Host头原样传递给后端这是最危险的情况。后端应用如果使用Host头来生成绝对URL比如在密码重置邮件里那么生成的链接就会包含evil.com导致用户点击后访问的是攻击者的网站。2.2 主要风险场景密码重置毒化应用使用Host头来构建重置密码链接https://$Host/reset?tokenxxx。攻击者提交重置请求时注入恶意Host导致用户收到的邮件链接指向攻击者服务器从而窃取token。缓存投毒如果存在缓存层如CDN、Nginx缓存攻击者可能通过注入恶意Host头将有害内容如恶意脚本缓存起来当其他用户访问同一URL时会收到被污染的内容。绕过访问控制某些应用的安全策略如CORS可能基于Host头进行校验。伪造的Host头可能绕过这些限制。SSRF攻击跳板如果后端应用信任Host头并以其值为目标发起内部请求攻击者可能将其指向内网敏感服务。我们的防御核心思路非常明确在Nginx层面对进入的请求进行严格的Host头校验只放行我们明确允许的合法域名其他一律拒绝并返回错误如444状态码直接关闭连接。3. Nginx防御配置实战详解理论清楚了我们进入实战环节。我将配置分为基础版和增强版并提供对应的正则表达式模板。3.1 基础防御精确域名匹配这是最简单、最直接的防御方式适用于域名明确且固定的场景。假设你的网站只允许通过www.yourdomain.com和yourdomain.com访问。你需要在Nginx的server配置块通常是监听80和443端口的那个中添加如下配置server { listen 80; listen 443 ssl http2; server_name www.yourdomain.com yourdomain.com; # 核心防御配置校验Host请求头 if ($host !~ ^(www\.)?yourdomain\.com$) { return 444; } # ... 其他配置SSL证书、root、index等... }配置解析与实操要点server_name指令它告诉Nginx这个server块处理哪些域名的请求。但请注意server_name主要用于请求进入时的初始路由匹配它不会验证客户端发来的Host头是否合法。即使Host头是evil.com如果server_name列表中没有匹配的Nginx会将其交给默认的server块通常是配置文件中第一个server块处理这很危险。因此仅靠server_name不够。if指令与$host变量$host是Nginx的内置变量其值按优先级取自请求行中的主机名、Host请求头、或与请求匹配的server_name。我们用它来获取客户端声称的Host。if ($host !~ ^(www\.)?yourdomain\.com$)这是一个条件判断。!~表示“不匹配”后面的正则表达式。正则表达式^(www\.)?yourdomain\.com$匹配以www.yourdomain.com或yourdomain.com结尾的字符串。return 444;这是Nginx特有的一个状态码意思是“无响应并关闭连接”。对于非法请求直接断开连接是最安全、最省资源的方式不给攻击者任何反馈信息。你也可以用return 403;禁止访问或return 400;错误请求但444更彻底。注意事项if的陷阱Nginx中的if指令在某些上下文中如location内有副作用被称为“邪恶的if”。但在server层用于检查$host是公认安全且常见的做法。转义点号在正则表达式中点号.是特殊字符匹配任意字符。要匹配字面意义的点必须使用反斜杠转义\.。测试配置修改后务必执行nginx -t测试语法然后nginx -s reload重载配置。使用curl测试curl -H “Host: evil.com” http://your-server-ip/应该被立即断开或无响应。3.2 增强防御正则表达式模板应对复杂场景实际生产环境往往更复杂你可能有多子域名、开发/测试环境、或者使用了泛域名证书。这时基础版的精确匹配就不够用了。下面是我总结的几个正则表达式模板你可以根据实际情况组合使用。场景一允许主域及其所有子域名如果你的业务模式是*.yourdomain.com例如app.yourdomain.com,api.yourdomain.com,blog.yourdomain.com等。if ($host !~ ^([a-zA-Z0-9-]\.)*yourdomain\.com$) { return 444; }正则解析([a-zA-Z0-9-]\.)*匹配零个或多个由字母、数字、连字符组成的子域名部分如app.、api.。这样就能覆盖任意深度的子域名。场景二允许多个特定主域及其子域名例如你的公司有company.com和company.net两个主域并且每个都支持任意子域名。if ($host !~ ^([a-zA-Z0-9-]\.)*(company\.com|company\.net)$) { return 444; }正则解析使用分组(company\.com|company\.net)和|或操作符来匹配多个主域。场景三允许特定子域名主域混合模式这是最常见的场景。例如你允许www.yourdomain.com、api.yourdomain.com、staging.yourdomain.com但不允许其他随意创建的子域名。if ($host !~ ^(www|api|staging)\.yourdomain\.com$|^yourdomain\.com$) { return 444; }正则解析这个表达式由两部分用|连接。^(www|api|staging)\.yourdomain\.com$匹配以www.、api.或staging.开头后接yourdomain.com的域名。^yourdomain\.com$匹配裸域名。整个表达式意思是Host必须是www.yourdomain.com、api.yourdomain.com、staging.yourdomain.com或yourdomain.com其中之一。场景四处理端口号有时请求会包含端口号如yourdomain.com:8080。Nginx的$host变量通常不包含端口号端口在$server_port变量中。但为了绝对严谨如果你的环境需要可以这样匹配if ($http_host !~ ^(www\.)?yourdomain\.com(:[0-9])?$) { return 444; }注意这里使用了$http_host变量它直接取自请求头可能包含端口。(:[0-9])?表示端口部分冒号后接数字出现0次或1次。实操心得在大多数情况下使用$host变量并忽略端口是更简单和安全的选择因为端口通常由监听指令listen 80;决定。优先考虑使用$host。4. 高级配置与最佳实践完成基础校验后我们可以进一步加固并优化配置结构。4.1 将防御逻辑抽象为独立配置文件如果你的服务器上有多个站点每个都写一遍if判断会很冗余。最佳实践是将这个校验逻辑放到一个独立的配置文件中然后在每个server块里用include指令引入。创建共享配置文件例如/etc/nginx/conf.d/validate_host.conf# validate_host.conf - Host头校验通用配置 if ($host !~ ^(www|api)\.yourdomain\.com$|^yourdomain\.com$) { # 可以记录日志以便审计 # access_log /var/log/nginx/host_validation.log main; return 444; }在各个站点的server块中引入server { listen 80; server_name www.yourdomain.com; include /etc/nginx/conf.d/validate_host.conf; # ... 其他站点特定配置 ... }这样做的好处是当校验规则需要修改时比如新增一个允许的子域名你只需要修改一个文件然后重载Nginx即可维护性大大提升。4.2 结合server_name与默认服务器块一个更优雅且高效的做法是利用Nginx的默认服务器default server机制。我们配置一个“兜底”的服务器块专门处理所有非法Host的请求。# 首先定义你的正常业务服务器块并明确指定server_name server { listen 80 default_server; # 注意这里不再是default_server listen 443 ssl http2; server_name www.yourdomain.com yourdomain.com api.yourdomain.com; # 这里可以不加if校验因为server_name已经明确 # ... 你的业务配置 ... } # 然后定义一个专门的默认服务器块放在正常业务块之后或单独文件中 server { listen 80 default_server; # 关键标记为默认服务器 listen 443 ssl http2 default_server; server_name _; # 使用通配符_匹配所有未在其他server块中定义的Host ssl_certificate /path/to/dummy.crt; # 可放置一个自签或无效证书 ssl_certificate_key /path/to/dummy.key; # 对所有非法Host请求直接返回444或一个简单的错误页 return 444; # 或者自定义错误页: return 403; }工作原理当请求到达时Nginx会先根据Host头在所有server块中寻找匹配的server_name。如果找到了比如是www.yourdomain.com就由对应的业务块处理。如果找不到匹配的比如evil.com那么这个请求就会由标记了default_server的块来处理我们在这个块里直接拒绝请求。这种方法避免了在每一个业务server块中使用if指令性能更优配置也更清晰。4.3 针对代理场景的特殊处理如果你的Nginx作为反向代理后面有Tomcat、Node.js等应用服务器你还需要确保传递给后端的Host头是安全的。location / { proxy_pass http://backend_server; # 显式设置传递给后端请求的Host头覆盖客户端可能注入的恶意头 proxy_set_header Host $http_host; # 或者更安全的使用固定的合法域名 # proxy_set_header Host www.yourdomain.com; # 同样重要的安全头部 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }关键点即使你在Nginx入口校验了Host在proxy_set_header时也不要直接使用$host或$http_host除非你确信它们已被前面的规则净化。一个更安全的做法是硬编码你的合法后端服务域名彻底杜绝任何注入可能。例如proxy_set_header Host “backend-internal.yourdomain.com”;5. 配置验证、测试与监控安全配置不上线测试等于没做。下面是一套完整的验证流程。5.1 配置语法测试与重载任何时候修改Nginx配置后第一步永远是sudo nginx -t如果输出syntax is ok和test is successful才能进行下一步。否则根据错误信息修正配置。测试通过后平滑重载配置不影响在线服务sudo nginx -s reload5.2 使用cURL进行攻击模拟测试准备一系列测试用例模拟攻击者的请求# 测试1合法请求 (应正常响应) curl -I http://your-server-ip/ -H “Host: www.yourdomain.com” # 测试2非法域名 (应返回444/403或被直接断开) curl -v http://your-server-ip/ -H “Host: evil.com” # 观察响应连接应被立即重置或返回444/403。 # 测试3子域名注入 (应被拒绝) curl -v http://your-server-ip/ -H “Host: www.yourdomain.com.evil.com” # 测试4IP地址直接访问 (如果你的配置不允许也应被拒绝) curl -v http://your-server-ip/ -H “Host: 203.0.113.10” # 测试5空Host头 (根据配置决定通常也应拒绝) curl -v http://your-server-ip/ --header “Host:”5.3 监控与日志审计将非法的Host请求记录下来有助于你发现攻击趋势和扫描行为。在防御性if块或默认server块中添加日志server { listen 80 default_server; server_name _; access_log /var/log/nginx/invalid_host.log main; return 444; }定期分析日志你可以使用awk、grep等工具或者接入ELK、Graylog等日志系统对/var/log/nginx/invalid_host.log进行监控统计非法请求的来源IP、频率等。# 简单统计非法Host的TOP IP awk ‘{print $1}’ /var/log/nginx/invalid_host.log | sort | uniq -c | sort -nr | head -205.4 常见问题排查实录问题1配置重载后合法请求也被拒绝了。排查首先检查正则表达式。最常见的错误是转义问题或逻辑运算符错误。使用在线正则表达式测试工具如 regex101.com反复验证你的表达式是否能精确匹配所有合法域名并排除非法域名。技巧在测试正则时把!~不匹配先改成~匹配然后重载Nginx用合法域名测试。如果合法域名能匹配成功说明正则本身没问题再改回!~。问题2使用了默认服务器块但好像没生效。排查确认default_server参数只出现在一个listen指令上对于每个IP:端口组合。如果有多个server块都声明了default_server只有其中一个会生效通常是配置文件中最先读取的那个。技巧使用nginx -T命令可以打印出所有加载的配置检查default_server的归属。问题3后端应用仍然收到了奇怪的Host头。排查检查你的proxy_set_header Host指令。如果你在Nginx层校验了Host但在代理时又原样传回了$http_host那么校验就白做了。确保代理时设置的Host头是你期望的后端服务地址。技巧在后端应用的访问日志中打印出接收到的Host头与Nginx的访问日志进行对比确认代理环节是否篡改。问题4HTTPS站点配置后用IP直接访问浏览器报证书错误。这是正常现象甚至是期望的效果。当用户用IP访问你的HTTPS服务时浏览器会尝试用IP去匹配SSL证书中的域名SAN显然匹配失败会抛出证书错误。这实际上阻止了通过IP的直接访问。你可以在默认服务器的443端口监听处配置一个通用的或自签名的证书来处理这类请求并返回错误页而不是让浏览器展示令人困惑的证书错误。但更常见的做法是在防火墙层面就限制只能通过域名访问。我个人在实际部署中倾向于采用“默认服务器块返回444” “业务服务器块明确server_name”的组合方案。它逻辑清晰性能损耗最小并且能有效处理各种边缘情况。对于反向代理架构务必记得在proxy_set_header时覆盖Host头将安全链条延伸到后端。这个5分钟的配置可能是你的Web应用安全体系中性价比最高的一环。