blog.dopana

Back

想象一下,你走进一家银行,它有两个大门:一个是毫无安保的木门(HTTP),另一个是配备防弹装甲的安检门(HTTPS)。

习惯上,很多用户会直接推开那个无安保的木门。当你走进去时,大堂经理提醒你:“请走旁边带防弹锁的安检门!”(HTTP 301/302 重定向)。

但如果此时正有一个窃贼守在木门外,在你被提醒之前就把你拦下并搜走财物呢?这就是传统 HTTPS 重定向机制面临的致命漏洞 —— 而 HSTS 的诞生,就是为了从源头上将这扇危险的木门彻底锁死。

问题所在:为什么仅靠 HTTPS 重定向是不够的?#

绝大多数人在访问网站时,不会在地址栏输入 https://,而是直接输入 example.com 或点击未加密的旧链接。

默认流程如下:

  1. 浏览器首先通过未加密的 HTTP 协议(80 端口)发出请求。
  2. Web 服务器收到后,返回 301 Moved Permanently 重定向到 https://...(443 端口)。
  3. 浏览器再建立安全的 HTTPS 加密连接。

在步骤 1中存在的时间差与明文传输,让攻击者有机可乘,实施 SSL 剥离攻击(SSL Stripping / SSL Downgrade Attack)。

sequenceDiagram
    autonumber
    actor User as 👤 用户
    participant Attacker as 🦹 攻击者 (中间人 / 公共WiFi)
    participant Server as 🏢 Web 服务器

    User->>Attacker: 1. 访问 http://example.com (明文 HTTP)
    Note over Attacker: 截获初始 HTTP 请求
    Attacker->>Server: 2. 攻击者代为建立安全 HTTPS 连接
    Server-->>Attacker: 3. 服务器返回加密网页内容
    Attacker-->>User: 4. 剥离 SSL,将明文 HTTP 网页发送给用户
    Note over User,Attacker: 账号密码、Session Cookie 被全盘窃取!

[!WARNING] 在公共 Wi-Fi(如咖啡厅、机场)环境下,黑客可以拦截初始的 HTTP 流量,自身与真实服务器进行 HTTPS 通信,而向你返回明文 HTTP。此时浏览器不会弹出任何证书警告,因为你访问的确实就是普通的 HTTP 网页!

解决方案:什么是 HSTS?#

HSTS(HTTP Strict Transport Security,HTTP 严格传输安全) 是由 Web 服务器返回给浏览器的一个响应头(Response Header),它指示浏览器:

“从现在开始,无论用户输入 http:// 还是点击 HTTP 链接,在向外发出任何网络数据包之前,必须在本地直接强制升级为 https://(内部 307 重定向)!”

开启 HSTS 之后:

  • 浏览器在客户端本地直接完成协议升级,不会发出任何明文 HTTP 数据包。
  • 如果网站的 SSL/TLS 证书失效或不受信任,浏览器会强制中断连接,且不提供“继续前往(不安全)”的跳过按钮,彻底粉碎中间人伪造证书的可能。
sequenceDiagram
    autonumber
    actor User as 👤 用户
    participant Browser as 🌐 浏览器 (启用 HSTS)
    participant Attacker as 🦹 攻击者 (中间人)
    participant Server as 🏢 Web 服务器

    User->>Browser: 输入 "http://example.com"
    Note over Browser: 检查 HSTS 缓存:强制执行 HTTPS!
    Browser->>Browser: 本地自动重定向为 "https://example.com" (307)
    Browser->>Server: 从第一毫秒起建立端到端 HTTPS 连接
    Note over Attacker: 完全失效!无法窥探或篡改加密数据包。
    Server-->>Browser: 安全返回加密响应

HSTS 响应头结构解析#

HSTS 的响应头语法非常直观:

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

指令详细说明:

指令作用说明示例
max-age=<seconds>浏览器在本地缓存并强制执行该 HTTPS 规则的有效期(秒)。max-age=31536000 (1 年)
includeSubDomains(可选) 将 HSTS 策略扩展应用到所有子域名(如 api.domain.com)。申请 Preload 必填
preload(可选) 申请将域名硬编码内置到各大浏览器的全网预加载列表中。彻底消除首次访问风险

HSTS Preload:解决“首次访问信任”盲区 (TOFU)#

尽管 HSTS 极其强悍,但依然存在所谓的 首次访问信任(Trust on First Use, TOFU) 缺陷:

  • 当用户在全新的设备或清空缓存的浏览器上第一次访问网站时,浏览器本地并没有 HSTS 记录。
  • 这第一条请求仍然会以明文 HTTP 发出。

HSTS Preload 预加载机制#

为了弥补这一漏洞,主流浏览器(Chrome、Firefox、Safari、Edge)共同维护了一份 HSTS Preload List(预加载列表):

  1. 网站配置符合规范的完整 HSTS Header。
  2. 网站管理员前往 hstspreload.org 提交域名。
  3. 审核通过后,该域名将被直接硬编码写入浏览器的源码与安装包中。
  4. 哪怕用户是这辈子第一次访问该域名,浏览器也会从第零秒起强制使用 HTTPS!
graph TD
    A[用户输入: example.com] --> B{是否存在于浏览器内置 Preload 列表?}
    B -->|是 - 浏览器内核内置| C[从一开始直接发起 HTTPS 连接]
    B -->|否| D{是否存在于浏览器本地 HSTS 缓存?}
    D -->|是 - 历史访问过| C
    D -->|否 - 全新首次访问| E[发出初始 HTTP 明文请求]
    E --> F[服务器响应 HTTPS + HSTS Header]
    F --> G[写入本地缓存供后续使用]

[!TIP] 顶级域名(TLD)如 .dev.app.page 在注册局层面就已经全局开启了 HSTS Preload。

常用 Web 服务器配置指南#

1. Nginx#

在监听 443 端口的 HTTPS server 块中添加:

nginx.conf
server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # SSL 证书配置...

    # 启用 HSTS (有效期1年,包含所有子域名,支持 Preload)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
nginx

2. Apache#

确保已启用 mod_headers 模块:

<VirtualHost *:443>
    ServerName example.com
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</VirtualHost>
apache

3. Caddy Server#

Caddyfile
example.com {
    header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    reverse_proxy localhost:3000
}
plaintext

4. Cloudflare CDN#

若使用 Cloudflare:

  1. 登录 Cloudflare 控制台 -> 选择目标域名。
  2. 点击 SSL/TLS -> Edge Certificates。
  3. 找到 HTTP Strict Transport Security (HSTS) -> 点击 Enable HSTS。
  4. 设定 Max-Age,勾选 Include SubdomainsPreload

开启 HSTS 的避坑与平滑迁移策略#

[!CAUTION] HSTS 无法快速撤销! 一旦设置了 includeSubDomainsmax-age=1年,如果有任何一个子域名(如 internal-admin.example.com)未配置有效 SSL 证书,用户在长达一年的时间内将完全无法访问该子域名。

建议按以下三阶段稳步推进:

  1. 第一阶段 — 短期冒烟测试:

    Strict-Transport-Security: max-age=300
    http

    (先设为 5 分钟,验证核心业务是否正常)

  2. 第二阶段 — 子域名验证:

    Strict-Transport-Security: max-age=86400; includeSubDomains
    http

    (维持 1 天,确认所有二级和三级域名 SSL 均正常)

  3. 第三阶段 — 生产长期固化并提交 Preload:

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

    (稳定运行后,前往 hstspreload.org 申请收录)

总结#

对比维度普通 301/302 重定向开启 HSTS
首个请求明文 HTTP 传输(易受中间人攻击)浏览器本地内部升级为 HTTPS
防 SSL Stripping❌ 无法防御✅ 彻底免疫
证书异常处理允许用户“忽略风险并继续访问”强制彻底拦截,无法跳过
网络开销额外产生 1 次重定向往返延迟本地 307 跳转,访问速度更快

配置 HSTS 仅需增加一行响应头,却能为网站带来极高的安全防线。立即检查并为你的网站开启 HSTS 吧!

参考资料#