return
在server
区块中
这使用 Gunicorn、Nginx 和 HTTPS 安全部署 Django 应用使用以下 HTTP 到 HTTPS 重定向块:
# Redirect HTTP to HTTPS
server {
server_name .supersecure.codes;
listen 80;
return 307 https://$host$request_uri;
}
我将其解释为对 HTTP 标头匹配的端口 80 上的每个 HTTP 请求执行 307 重定向Host
。
return
在location
Mozilla SSL 配置生成器使用以下块执行相同的 HTTP 到 HTTPS 重定向(例如这里):
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
return 301 https://$host$request_uri;
}
}
我假设它被放在一个location
块中,以防有人想通过 HTTP 提供其他东西(例如,静态资产),那么添加它的工作量就会减少。
return
在if
区块中
这是来自生产中的旧服务器:
server {
if ($host = some.org) {
return 301 https://$host$request_uri;
}
listen 80;
server_name some.org;
return 404;
}
我只能猜测为什么会被if
阻止,所以我假设Host
HTTP 标头明确检查了正确的域,如果不匹配,则返回 404。否则,它将被踢到server
设置 TLS 的阻止。
使用这种形式有什么好处吗?
编辑:找到了Certbot 的 Nginx 插件导致重定向的安全问题在 Let's Encrypt 论坛上发帖,解释了区块return
内部如何if
更安全。仍然不明白具体原因,但这是一个开始。还有开放的certbot 问题 #9705问同样的事情。
编辑-2:解释为什么“裸露”return 301
有害的相关段落发布在下面。(@艾萨·约基宁:我选择不回答的原因是因为我不明白这一点。)
Certbot 在 Nginx 中创建 HTTP 到 HTTPS 重定向的方式是通过以下配置指令:
return 301 https://$host$request_uri;
这样做的潜在问题是,它依赖于标头的值来
Host
确定将请求重定向到何处。如果在 Nginx 配置中将这样的指令添加到 HTTP 流量的默认服务器块,则用户可以修改Host
其请求的标头以将其重定向到任意域。就其本身而言,这不是问题,因为重定向到任意域只会发送给将该域放入Host
其请求标头的用户。如果攻击者可以欺骗某个缓存来存储来自 Nginx 的重定向,并将其提供给尝试通过正常方式使用未修改的
Host
标头连接到服务器的用户,则会出现问题。如果可以做到这一点,攻击者可以将来自该缓存用户的流量重定向到他们选择的域。由于虚拟主机的盛行,能够执行此操作的缓存必须有自己的错误或配置错误。Certbot 添加的重定向本身无法利用,但当与其他具有自身错误、漏洞或配置错误的软件结合使用时,可能会成为安全问题。
答案1
背景很重要
这真的取决于你的需求
仅限/第一个/default_server
当你只有单个server {}
TCP 端口 80 上的纯 HTTP 阻止,或者您已隐式/显式配置默认/全部捕获服务器 在还包含用于虚拟服务器的附加服务器块的配置中,服务器通常不仅处理您实际托管的站点的请求。
该服务器还将处理对您的裸 IP 地址的请求,对您的 IP 地址的先前用户尚未清理但仍可解析为您的 IP 地址的 DNS 条目的请求,和/或可能存在通配符 DNS 条目。
您不太可能拥有针对您的 IP 地址、您从未拥有和/或不再托管的 DNS 名称的有效 TLS 证书,并且您可能没有与通配符 DNS 记录匹配的通配符 TLS 证书。
当您在该服务器中配置无条件参数化重定向时,例如:
return 301 https://$host$request_uri;
它会将每个请求重定向到原始请求的 HTTPS 版本,即使对于您没有有效 TLS 证书的网站也是如此。
因此,这可能会导致网站访问者出现错误。
替代方法:
这是一个观点/风格和偏好的问题。
不要使用参数化重定向, 但重定向到特定网站,您确实拥有有效的 TLS 证书。
return 301 https://www.example.com$request_uri;
有些人建议始终设置一个默认服务器,以便对所有不合格的请求返回通用错误。它们不会从该 default_server 重定向到其他地方。并明确创建一个或多个附加服务器来处理“真实访客”针对托管在该 Web 服务器上的实际网站。这样他们就不会向访问裸 IP 地址的访问者透露他们运营的网站。不同的服务器还允许您设置不同的日志文件,以跟踪您的不同服务器仍在处理多少个普通 HTTP 请求。
server { listen 80 default_server; server_name _; # This is just an invalid value which will never trigger on a real hostname. server_name_in_redirect off; # return a 404 error response to all unqualified requests return 404; } server { listen 80; server_name example.net www.example.net; # always redirect plain http to https # and redirect bare domain to www # if works as desired - change temporary to permanent redirect return 301 https://www.example.net$request_uri; }
您发布的块示例
if
与上面的示例大致相同,并且是一种过滤和重定向到仅对您具有 TLS 站点的站点的 HTTPS 请求的方法,但在单个服务器块中:server { if ($host = some.org) { return 301 https://$host$request_uri; } listen 80; server_name some.org; return 404; }
注意:我个人不会在这个构造中使用参数化重定向,因为 nginx
https:/some.org$request_uri;
无论如何都会重定向到。
附加服务器
server {
#default server
}
server {
#first additional virtual server
}
server {
#second additional virtual server
}
当您已经拥有包含用于虚拟服务器的附加服务器块的配置时,仅当网站访问者发送正确的Host:
标头时才会使用这些服务器块,并且这些服务器不必处理不合格的请求。
在附加/虚拟服务器中,if ($host = some.org)
条件重定向将完全是多余的,因为该虚拟服务器无论如何只会处理针对该特定主机的请求。
server {
#default server
}
server {
#first virtual server
listen 80;
server_name example.org;
# Although valid syntax logically this "if" construct is completely redundant here
# this server will only process requests for example.org anyway
if ($host = example.org) {
return 301 https://$host$request_uri;
}
server {
#second virtual server
listen 80;
server_name example.com www.example.com;
# this redirects www.example.com to https://www.example.com
# this redirects example.com to https://example.com
# that only makes sense when those are either completely different sites
# OR they are one and the same.
# The parameterised redirect doesn't make sense when subsequently
# https://example.com redirects to https://www.example.com or vice-versa
return 301 https://$host$request_uri;
# The above is functionally completely equivalent to
# enclosing the redirect in a location block as done below
location / {
return 301 https://$host$request_uri;
}