如何禁用 Django 的无效 HTTP_HOST 错误?
Posted
技术标签:
【中文标题】如何禁用 Django 的无效 HTTP_HOST 错误?【英文标题】:How to disable Django's invalid HTTP_HOST error? 【发布时间】:2013-08-15 17:50:55 【问题描述】:自从我部署了一个运行 Django 1.7 alpha 的站点(从 Git 签出)以来,我偶尔会收到标题如下的错误消息:
“无效的 HTTP_HOST 标头:'xxx.xxx.com'”
我意识到这是由于Host:
HTTP 标头被设置为ALLOWED_HOSTS
中未列出的主机名。但是,我无法控制某人何时以及多久使用伪造的主机名向服务器发送请求。因此,我不需要一堆错误电子邮件让我知道其他人正在尝试做一些可疑的事情。
有什么方法可以禁用此错误消息?项目的日志记录设置如下所示:
LOGGING =
'version': 1,
'disable_existing_loggers': False,
'filters':
'require_debug_false':
'()': 'django.utils.log.RequireDebugFalse'
,
'handlers':
'mail_admins':
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
,
'loggers':
'django.request':
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
,
【问题讨论】:
为什么在生产中使用 alpha 版本? @Burhan:因为在最新的稳定版本之后修复了几个重要的错误。 你如何托管它(例如通过 Apache 的 WSGI)?我很想在它击中 Django 之前弄清楚如何阻止它 @Foon: Apache 通过 mod_wsgi。 有谁知道这次攻击的名称,或者如果有人设法使用您不打算使用的主机名查询您的网站,为什么会很重要?我的意思是,因为他们正在这样做,我认为肯定存在一些漏洞,但对我来说它可能是什么并不明显。 【参考方案1】:你可以用类似的东西使那个特定的 SuspiciousOperation 静音
'loggers':
'django.security.DisallowedHost':
'handlers': ['null'],
'propagate': False,
,
更多参考请参见https://docs.djangoproject.com/en/dev/topics/logging/#django-security
编辑
您还需要添加一个“空”处理程序:
'handlers':
'null':
'level': 'DEBUG',
'class': 'logging.NullHandler',
,
可能您只需要添加它并修改错误级别(将 DEBUG 替换为 'ERROR')。
如往常一样,请参阅documentation 了解完整的语法和语义。
【讨论】:
这似乎不起作用。我从 mod_wsgi 收到一个错误:"ValueError: Unable to configure logger 'django.security.DisallowedHost': Unable to add handler 'null': 'null'"
您是否有文档所述的名为“null”的处理程序?我将修改我的答案以指向一个示例。
还是不行。这是我正在使用的确切日志配置:quickmediasolutions.com/pastebin/15/…【参考方案2】:
您可以将其添加到日志记录配置的 loggers
部分:
'django.security.DisallowedHost':
'handlers': ['mail_admins'],
'level': 'CRITICAL',
'propagate': False,
,
这会将日志记录阈值设置为高于 Django 在检测到SuspiciousOperation
时使用的ERROR
级别。
或者,您可以使用例如FileHandler
记录这些事件而无需通过电子邮件发送给您。例如,要为这些特定事件使用专用文件,您可以将其添加到 handlers
部分:
'spoof_logfile':
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': '/path/to/spoofed_requests.log',
,
然后在loggers
部分使用它:
'django.security.DisallowedHost':
'handlers': ['spoof_logfile'],
'level': 'ERROR',
'propagate': False,
,
注意Django docs中提出的建议,要使用
'django.security.DisallowedHost':
'handlers': ['null'],
'propagate': False,
,
取决于您运行 Python 2.7 或更高版本 - 在 2.6 上,logging
没有 NullHandler
。
【讨论】:
【参考方案3】:您不应忽略此错误。相反,您应该在请求到达您的 Django 后端之前拒绝该请求。要拒绝未设置 HOST
的请求,您可以使用
SetEnvIfNoCase Host .+ VALID_HOST
Order Deny,Allow
Deny from All
Allow from env=VALID_HOST
或强制匹配特定域(example.com)
SetEnvIfNoCase Host example\.com VALID_HOST
Order Deny,Allow
Deny from All
Allow from env=VALID_HOST
【讨论】:
也就是说,您只使用 Apache。使用 nginx 转发请求,参考这个答案:***.com/a/17477436/188614 Mark - 我需要启用什么才能获得您在上面显示的这种行为?如果我使用传统的 apache 2.x 还会进入什么配置文件? 将此代码添加到我的虚拟主机文件会导致错误,例如“此处不允许订购”或“此处不允许订购”。 @tufelkinder :您可以将代码放在virtualhost
配置中的新 <Directory / > </Directory>
指令中。
您还应该提供准确放置此配置的位置,对于刚接触 Apache 的人来说,这会很有帮助。【参考方案4】:
这是 NGINX 示例,它应该可以防止您的 django 接收垃圾请求。
server
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
return 444;
server
listen 80;
# This will keep Django from receiving request with invalid host
server_name <SERVER_IP> your.domain.com;
...
【讨论】:
官方文档 (nginx.org/en/docs/http/…) 建议在这种情况下返回 444 状态码。 另外,它应该是default_server
而不是 default
(与上面相同的链接供参考),至少从今天开始 :)
如果使用 HTTPS,不要忘记重复与上面相同的服务器块,除了端口 443 和指定的 ssl 证书。只是浪费了一个小时试图弄清楚为什么我不断收到错误消息。
在答案中添加了 HTTPS 部分。【参考方案5】:
在到达 Django 之前阻止带有无效 Host 标头的请求的另一种方法是使用带有 <VirtualHost>
的 default Apache 配置,它只会返回 404。
<VirtualHost *:80>
</VirtualHost>
如果您将其定义为您的第一个虚拟主机(例如在 000-default.conf 中),然后在其后面添加您的“真实”<VirtualHost>
,并添加您想要的 <ServerName>
和任何 ServerAlias> 条目为了匹配,Apache 将为任何带有 Host
标头但与 <ServerName>
或您的 <ServerAlias>
条目之一不匹配的请求返回 404。确保首先定义默认值 404 <VirtualHost>
的关键,可以通过文件名 ('000') 或配置文件中的第一个条目来定义。
我比上面流行的解决方案更喜欢这个,因为它非常明确且易于扩展。
【讨论】:
这不会为我返回 404,而只是允许访问默认文档根目录中的所有内容、列出文件等。是否需要特定的 404 响应指令来使其更安全? 【参考方案6】:我还不能发表评论,但由于 Order Deny, Allow 已被弃用,使用当前 Require 指令在虚拟主机中执行此操作的方法是:
<Directory /var/www/html/>
SetEnvIfNoCase Host example\.com VALID_HOST
Require env VALID_HOST
Options
</Directory>
【讨论】:
【参考方案7】:在setting.py中设置:
ALLOWED_HOSTS = ['yourweb.com']
【讨论】:
这实际上并没有解决 OP 提出的问题。正如 OP 所说:“但是,我无法控制某人何时以及多久使用伪造的主机名向服务器发送请求。”问题是每次伪造的主机名通过时,OP 都会以繁琐的方式(电子邮件)收到通知。 OP 想要的是获得不那么具有侵入性的通知。编辑ALLOWED_HOSTS
不是这里的解决方案。【参考方案8】:
如果您只是想隐藏或禁用警告,则此页面上的其他答案是正确的。如果您有意允许每个主机名使用 *
的特殊值作为 ALLOWED_HOSTS
设置。
注意:这可能会引入安全漏洞。
在某些情况下,Django 使用客户端提供的 Host 标头来构造 URL。虽然这些值已被清理以防止跨站点脚本攻击,但伪造的 Host 值可用于跨站点请求伪造、缓存中毒攻击和电子邮件中的中毒链接。
因为即使看似安全的 Web 服务器配置也容易受到虚假 Host 标头的影响,Django 会根据 django.http.HttpRequest.get_host() 方法中的 ALLOWED_HOSTS 设置验证 Host 标头。
要完全阻止主机名检查,请将以下行添加到您的 settings.py
:
ALLOWED_HOSTS = ['*']
来源:https://github.com/django/django/blob/33c365781abbcc1b21a31b31d95d344a174df0d5/django/http/request.py#L653-L668
def validate_host(host, allowed_hosts):
"""
Validate the given host for this site.
Check that the host looks valid and matches a host or host pattern in the
given list of ``allowed_hosts``. Any pattern beginning with a period
matches a domain and all its subdomains (e.g. ``.example.com`` matches
``example.com`` and any subdomain), ``*`` matches anything, and anything
else must match exactly.
Note: This function assumes that the given host is lowercased and has
already had the port, if any, stripped off.
Return ``True`` for a valid host, ``False`` otherwise.
"""
return any(pattern == '*' or is_same_domain(host, pattern) for pattern in allowed_hosts)
【讨论】:
这个答案应该包括一个警告,它会引入潜在的安全漏洞 @Zach 我添加了一条警告,包括有关潜在安全问题的官方文档【参考方案9】:使用 Apache 2.4,无需使用 mod_setenvif。 HTTP_HOST 已经是一个变量,可以直接求值:
WSGIScriptAlias / /path/to/wsgi.py
<Directory /path/to>
<Files wsgi.py>
Require expr %HTTP_HOST == "example.com"
</Files>
</Directory>
【讨论】:
【参考方案10】:对于多个有效主机,您可以:
SetEnvIfNoCase Host example\.com VALID_HOST
SetEnvIfNoCase Host example2\.com VALID_HOST
SetEnvIfNoCase Host example3\.com VALID_HOST
Require env VALID_HOST
【讨论】:
【参考方案11】:django docs 专门解决了这个问题。他们建议将其放入您的日志记录设置中
LOGGING =
# ...
"handlers":
# ...
"null":
"class": "logging.NullHandler",
,
,
"loggers":
# ...
"django.security.DisallowedHost":
"handlers": ["null"],
"propagate": False,
,
,
另外,如果你使用的是 Sentry,你需要添加这个以防止 Sentry 捡起它:
from sentry_sdk.integrations.logging import ignore_logger
ignore_logger("django.security.DisallowedHost")
【讨论】:
它不会也阻止电子邮件错误警报吗?他们停止为我工作 @Mobeen 这个答案的目标是禁用不允许的主机(包括电子邮件)的所有错误日志记录 对我来说:内部服务器错误邮件在此之后也被阻止了 @Mobeen Django 日志记录允许您配置如何处理不同类型的错误。如果您还没有设置任何其他记录器,请添加一个默认记录器,如下所示:***.com/a/5439502/2800876 知道了,非常感谢您在 cmets 中的回答【参考方案12】:这是防止 django 接收此类请求所需的 NGINX 块。
server
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
return 444;
【讨论】:
【参考方案13】:还要通过 Apache 处理以下错误:
HTTP_HOST 标头无效:“_my.domain.com”。根据 RFC 1034/1035,提供的域名无效。我们可以使用正则表达式,
^[^_]+
将匹配一个包含 1 个或多个字符的字符串,该字符串包含 子域 中除下划线以外的任何字符,如下例所示。
我们可以将它应用到 wsgi.py 文件
<VirtualHost xxx.xxx.xxx.xxx:XX>
...
SetEnvIfNoCase Host "^[^_]+\.my-domain\.com" VALID_HOST
<Files wsgi.py>
<RequireAll>
Require all granted
Require env VALID_HOST
</RequireAll>
</Files>
...
</VirtualHost>
与Require expr:
<VirtualHost xxx.xxx.xxx.xxx:XX>
...
<Files wsgi.py>
Require expr %HTTP_HOST =~ m#^[^_]+\.my-domain\.com#
</Files>
...
</VirtualHost>
或者我们可以使用 <Location "/">
,这是一种将配置应用到整个服务器的简单方法。
<VirtualHost xxx.xxx.xxx.xxx:XX>
...
SetEnvIfNoCase Host "^[^_]+\.my-domain\.com" VALID_HOST
<Location />
<RequireAll>
Require all granted
Require env VALID_HOST
</RequireAll>
</Location>
...
</VirtualHost>
来自 Apache 文档:
何时使用
<Location "/">
使用
将指令应用于位于 文件系统。对于文件系统中的内容,使用 和 。 是个例外,这是一种简单的方法 将配置应用到整个服务器。
答案基于Apache Module mod_setenvif 和how to block persistent requests from a particular robot。
【讨论】:
以上是关于如何禁用 Django 的无效 HTTP_HOST 错误?的主要内容,如果未能解决你的问题,请参考以下文章
Django 的 SuspiciousOperation 无效的 HTTP_HOST 标头
Django 错误(外部 IP):无效的 HTTP_HOST 标头:'*.domain.com'
避免使用 Django + Elastic Beanstalk 获得“无效的 HTTP_HOST 标头:'localhost'”消息
Django 错误:无效的 HTTP_HOST 标头:u'/run/myprojectname/gunicorn.sock:'