尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

[Django] DisallowedHost突然爆发?ALLOWED_HOSTS=‘*‘为什么没用+中间件根治方案(附代码)

[Django] DisallowedHost突然爆发?ALLOWED_HOSTS=‘*‘为什么没用+中间件根治方案(附代码)
📅 发布时间:2026/6/21 6:34:07

Django 生产环境 DisallowedHost 突然爆发?ALLOWED_HOSTS=['*'] 为什么没用?

TL;DR:你的 Django 项目突然开始报DisallowedHost,日志里 Host 是一个奇怪的下划线_,而你明明设了ALLOWED_HOSTS = ['*']。问题出在 Django 的 Host 校验分两步——先做 RFC 1034/1035 域名合法性校验,通过才查 ALLOWED_HOSTS。_不是合法域名,连第一关都过不去。根治方案:一个 15 行的中间件,在 CommonMiddleware 之前拦截。


1. 事故现场

某天早上,含光博客的日志/邮件告警里出现了这样一个错误(关键信息已标注):

DisallowedHostat/InvalidHTTP_HOSTheader:'_'.ThedomainnameprovidedisnotvalidaccordingtoRFC1034/1035.# ⬇ 三行关键证据HTTP_HOST='_'HTTP_USER_AGENT='Hello from Palo Alto Networks, find out more about our scans...'HTTP_X_FORWARDED_FOR='203.0.113.1'ALLOWED_HOSTS=['*','127.0.0.1','example.com']# ← 明明设了通配符

第一反应:ALLOWED_HOSTS = ['*']不是应该放行所有 Host 吗?怎么还报 DisallowedHost?


2. 根因:Django 的 Host 校验是两步,不是一步

翻 Django 源码django/http/request.py,get_host()方法的逻辑是:

defget_host(self):host=self._get_raw_host()# 从 HTTP_HOST / SERVER_NAME 取# 第一步:RFC 1034/1035 域名合法性校验host,port=split_domain_port(host)ifhostandnothost_validation_re.match(host):raiseDisallowedHost(f"Invalid HTTP_HOST header:{host!r}.")# 第二步:ALLOWED_HOSTS 白名单比对ifnotvalidate_host(host,settings.ALLOWED_HOSTS):raiseDisallowedHost(f"Invalid HTTP_HOST header:{host!r}.")returnhost

关键发现:第一步的host_validation_re正则在对ALLOWED_HOSTS生效之前就已经跑了。

_为什么通不过这个正则?因为 RFC 1034/1035 规定域名只能包含字母、数字和连字符(-),_(下划线)不在合法字符集中。所以_直接在第一关被毙,根本走不到第二步的通配符*。

Host 值第一步(RFC 校验)第二步(ALLOWED_HOSTS)结果
www.baidu.com✅✅200
_.com❌(含_)不会走到DisallowedHost
纯数字 IP1.2.3.4✅✅200
单下划线_❌不会走到DisallowedHost
example.com✅✅200

ALLOWED_HOSTS = ['*']保证的是第二步永远通过,但救不了第一步的 RFC 合规性检查。


3. 这些畸形 Host 从哪来?

互联网扫描器(Shodan、Censys、Palo Alto Cortex Xpanse 等)在持续探测公网资产。它们发请求时可能会:

  • 设Host: 你的IP(用 IP 而非域名)
  • 设Host: _(占位符/探测标记)
  • 设Host: <script>alert(1)</script>(XSS 探测)
  • 完全不发 Host 头(某些 HTTP/1.0 客户端)

这些都不是攻击——只是常规资产扫描。但 Django 默认会为每个 DisallowedHost 记录 traceback + 发邮件给 ADMINS,日志和邮箱很快会被撑满。


4. 解决方案对比

方案拦截点副作用推荐度
方案 A:Django 自定义中间件应用层(Django)无⭐⭐⭐⭐⭐
方案 B:Nginx/Caddy 过滤反向代理层需 Web Server 配合⭐⭐⭐⭐
方案 C:修改 ALLOWED_HOSTS配置治标不治本(见第 2 节)⭐
方案 D:忽略日志无核弹级——真正的攻击 Host 注入也会被掩盖❌

推荐方案 A,原因是: 1. 不依赖 Web Server(含光博客的 Nginx 是 frp 穿透进来的,直接配 Nginx 有坑) 2. 15 行代码,无外部依赖 3. 精确控制——只拦截畸形 Host,正常请求零影响


5. 实现:BlockBadHostMiddleware

在 Django 项目的middleware.py(或任何 middleware 文件)中添加:

importrefromdjango.httpimportHttpResponse# RFC 1034/1035 合法域名正则# 匹配:example.com / sub.example.com / localhost / 纯 IPv4# 不匹配:_ / _something / -bad.com_HOST_RE=re.compile(r"^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?"r"(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$")classBlockBadHostMiddleware:"""在 CommonMiddleware 之前拦截无效 Host,避免 DisallowedHost traceback"""def__init__(self,get_response):self.get_response=get_responsedef__call__(self,request):host=request.META.get("HTTP_HOST","")ifhostandnot_HOST_RE.match(host.split(":")[0]):returnHttpResponse("Bad Request",status=400)returnself.get_response(request)

然后在settings.py的MIDDLEWARE列表最前面插入:

MIDDLEWARE=['yourapp.middleware.BlockBadHostMiddleware',# ← 必须第一位!'django.middleware.security.SecurityMiddleware',# ... 其他中间件]

为什么必须第一位?Django 中间件按列表顺序执行。CommonMiddleware(包含get_host()调用)如果在BlockBadHostMiddleware之前执行,拦截器就没机会跑了。


6. 验证

# 畸形 Host → 400$curl-s-o/dev/null-w"%{http_code}"http://127.0.0.1:8770/-H"Host: _"400# 正常域名 → 200$curl-s-o/dev/null-w"%{http_code}"http://127.0.0.1:8770/-H"Host: www.baidu.com"200

畸形 Host 被静默拦截为 400,不再触发 traceback 和邮件告警。正常请求零影响。


7. 延伸:为什么 DNS 允许下划线但 HTTP Host 不允许?

这里有一个容易混淆的点:

  • DNS 层面:SRV 记录(_http._tcp.example.com)和 DKIM/SPF 等确实使用下划线,RFC 2181 明确说"DNS 不对 label 内容做限制"
  • HTTP Host 头层面:RFC 952 + RFC 1123(主机名规范)禁止下划线。Django 的host_validation_re遵循的是主机名规范,不是 DNS 规范

所以_dmarc.example.com作为 DNS 记录是合法的,但作为 HTTP Host 头传给 Django 就会被拒绝——除非你在前面放了这个中间件。


8. 总结

你看到的真实原因修法
DisallowedHost: '_'扫描器发的畸形 Host自定义中间件,返回 400 而不是抛异常
ALLOWED_HOSTS=['*']却没用*只管第二步,管不了第一步 RFC 校验中间件插在 MIDDLEWARE 第一位
日志/邮箱被撑满每个畸形请求都触发 traceback + 邮件拦截后只返回 400,无 traceback

这个错误在生产环境非常常见——任何暴露在公网的 Django 项目都会被扫描器光顾。上述 15 行中间件可以永久解决这个问题。


本文代码已在 Django 5.2 + Gunicorn 生产环境中验证通过。如果你也有类似的排查经验,欢迎在评论区交流。

相关新闻

  • Pandas apply() 实战避坑指南:性能、类型与索引三大陷阱
  • 5分钟掌握英雄联盟内存换肤:R3nzSkin终极使用指南
  • LPC21xx/22xx Flash编程与代码保护:ISP/IAP实战与CRP避坑指南

最新新闻

  • 天津全城黄金回收机构横评:合扬拿下 2026 天津 TOP1,资质合规流程全公开 - 开心测评
  • 网盘直链下载助手完整教程:告别限速,九大网盘一键高速下载
  • Python3+RIDE+RobotFramework自动化测试框架搭建与实战指南
  • 2026年6月国内优质的非能动氢气消除厂家推荐,催化器转化器/消氢催化剂,非能动氢气消除服务企业口碑推荐 - 品牌推荐师
  • 深圳2026年正规的沙盘定制生产厂哪家更值得选实力参考 - mypinpai
  • 怎么把照片改成413*626像素?2026照片像素精准调整工具指南 - 像素测评

日新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号