HTTP 严格传输安全(HSTS)与 NGINX

Netcraft 当时对其监测的 SSL/TLS 网站发表了一项研究,发现其中只有 5% 正确实现了 HTTP 严格传输安全(HSTS)。本文介绍如何配置 NGINX 和 NGINX Plus 来实施 HSTS 策略。

什么是 HSTS?

HTTPS(使用 SSL 或 TLS 加密的 HTTP)是保障网站流量安全的重要措施,使攻击者很难截获、修改或伪造用户与网站之间的流量。

当用户手动输入域名,未加 http:// 或 https:// 前缀,或点击普通 http:// 链接时,发往网站的第一个请求会以未加密的普通 HTTP 发送。多数安全网站会立即返回重定向,将连接升级为 HTTPS。但处于合适位置的攻击者可以发动中间人(MITM)攻击,截获最初的 HTTP 请求,继而控制用户会话。

HSTS 通过告知浏览器“该域名只能使用 HTTPS 访问”来应对这种潜在漏洞。即使用户输入或点击普通 HTTP 链接,浏览器也会强制将连接升级为 HTTPS:

原文 Chrome 开发者工具截图:HSTS 策略产生内部重定向,将 HTTP 升级为 HTTPS
Chrome 开发者工具展示 HSTS 策略如何通过内部重定向把 HTTP 升级为 HTTPS。图片来自原文。

HSTS 如何工作?

安全的 HTTPS 网站通过发送以下 HTTP 响应头发布 HSTS 策略:

Strict-Transport-Security: max-age=31536000

浏览器在 HTTPS 网站收到这个响应头后,就会“记住”该域名只能使用 HTTPS(SSL 或 TLS)访问,并将这条信息缓存 max-age 指定的时长,通常是 31,536,000 秒,约等于一年。

可选参数 includeSubDomains 告知浏览器,策略也适用于当前域名的所有子域:

Strict-Transport-Security: max-age=31536000; includeSubDomains

例如,https://www.example.com 的 HTML 响应可以包含对 https://example.com 上某个资源的请求,以确保为 example.com 的所有子域设置 HSTS。

在 NGINX 中配置 HSTS

在 NGINX 中设置 Strict Transport Security(STS)响应头相当直接:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

always 参数确保所有响应都设置该响应头,包括内部生成的错误响应。旧版 NGINX(1.7.5 之前)或 NGINX Plus R5 之前的版本不支持 always,因此不会在内部生成的错误响应中设置该头。

add_header 指令的继承规则

NGINX 配置块会从外层配置块继承 add_header 指令,所以只需在顶层 server 块中放置该指令。有一个重要例外:如果某个块自身包含 add_header 指令,它就不会继承外层配置块的响应头,需要重新声明所有 add_header 指令:

server {
    listen 443 ssl;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    # This 'location' block inherits the STS header
    location / {
        root /usr/share/nginx/html;
    }

    # Because this 'location' block contains another 'add_header' directive,
    # we must redeclare the STS header
    location /servlet {
        add_header X-Served-By "My Servlet Handler";
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        proxy_pass http://localhost:8080;
    }
}

谨慎测试 HTTP 严格传输安全

客户端收到 HSTS 策略后,会在 max-age 指定的时间内缓存它。在此期间,浏览器拒绝通过未加密的 HTTP 访问该服务,也拒绝允许证书错误的例外(前提是网站此前提供过有效、可信的证书)。如果策略包含 includeSubDomains,这些限制也适用于所有子域。

撤销 HSTS 策略、进而移除网站或服务的 HTTPS 版本非常困难。测试 HSTS 时,应使用很短的 max-age,并确认你能接受其影响,以及持续维护 HTTPS 网站的责任。首次上线时也应保持较短的 max-age,确信配置正确后再增加。

每个 HTTPS 响应都需要 STS 头吗?

目标是在用户开始 HTTPS 会话时,尽早向其提供 HSTS 策略。如果会话期间用户没有收到该策略,之后仍可能遭遇 HTTP 劫持攻击。

浏览器只需看到一次 STS 头,因此严格来说,并不需要把它加入每个 location 块和每个响应。但只在首页或登录页加入,通常不够;如果仅在可缓存的响应中加入,客户端可能看不到它。应在合理范围内覆盖尽可能多的 URL,尤其关注动态、不可缓存的内容。

同时提供网站的 HTTP 与 HTTPS 版本

一些网站在同一个 NGINX 或 NGINX Plus 服务中同时提供 HTTP 和 HTTPS,让用户可以通过任一协议访问内容:

server {
    listen  80;
    listen  443 ssl;
    # ...
}

启用 HSTS 时,这样做并不合适,因为不应让用户通过 HTTP 访问内容。相反,应将所有 HTTP 访问重定向到 HTTPS:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    # Discourage deep links by using a permanent redirect to home page of HTTPS site
    return 301 https://$host;

    # Alternatively, redirect all HTTP links to the matching HTTPS page
    # return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name www.example.com;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

加强 HSTS

客户端在指定的 max-age 期间内看到过相关域名的 STS 头后,就能获得防止 HTTP 截获的保护。

但是,HSTS 并不能完美解决 HTTP 会话劫持。如果用户通过 HTTP 访问受 HSTS 保护的网站,并且属于下列情况,仍可能遭遇攻击:

  • 从未访问过该网站。
  • 最近重新安装了操作系统。
  • 最近重新安装了浏览器。
  • 换用了新的浏览器。
  • 换用了新的设备,例如手机。
  • 清除了浏览器缓存。
  • 很久没有访问网站,max-age 已经过期。

来源:Netcraft。

为解决这一问题,Google 维护一份“HSTS 预加载列表”,记录使用 HSTS 并向 hstspreload.appspot.com 提交域名的网站及子域。该列表随主要浏览器分发,并硬编码到浏览器中。客户端访问列表里的域名时,会自动使用 HTTPS,拒绝通过 HTTP 访问。

原文随后提醒:一旦设置 STS 头或将域名提交到 HSTS 预加载列表,就无法移除;它将域名改为通过 HTTPS 提供服务形容为单向决定。

延伸阅读

有关 HSTS 的详细信息,参阅:

如果正在考虑为 NGINX 配置添加 STS 头,也可以一并考虑原文提到的其他安全相关 HTTP 响应头,例如 X-Frame-Options 和 X-XSS-Protection。这是原文当年的建议,具体选用应结合当前浏览器和安全规范。

来源与版权

原文:HTTP Strict Transport Security (HSTS) and NGINX;作者 Owen Garrett;发布日期 2016 年 3 月 23 日。© F5, Inc.,保留所有权利。本中文版本依据转载授权翻译;未执行文中配置。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容