共计 3742 个字符,预计需要花费 10 分钟才能阅读完成。
导读 | 在网站全站 HTTPS 后,如果用户手动敲入网站的 HTTP 地址,或者从其它地方点击了网站的 HTTP 链接,通常依赖于服务端 301/302 跳转才能使用 HTTPS 服务。而第一次的 HTTP 请求就有可能被劫持,导致请求无法到达服务器,从而构成 HTTPS 降级劫持。这个问题目前可以通过 HSTS(HTTP Strict Transport Security,RFC6797) 来解决。 |
HSTS(HTTP Strict Transport Security) 是国际互联网工程组织 IETF 发布的一种互联网安全策略机制。采用 HSTS 策略的网站将保证浏览器始终连接到该网站的 HTTPS 加密版本,不需要用户手动在 URL 地址栏中输入加密地址,以减少会话劫持风险。
Strict-Transport-Security: max-age=expireTime [; includeSubDomains] [; preload]
- max-age,单位是秒,用来告诉浏览器在指定时间内,这个网站必须通过 HTTPS 协议来访问。也就是对于这个网站的 HTTP 地址,浏览器需要先在本地替换为 HTTPS 之后再发送请求。
- includeSubDomains,可选参数,如果指定这个参数,表明这个网站所有子域名也必须通过 HTTPS 协议来访问。
- preload,可选参数,一个浏览器内置的使用 HTTPS 的域名列表。
虽然 HSTS 可以很好的解决 HTTPS 降级攻击,但是对于 HSTS 生效前的首次 HTTP 请求,依然无法避免被劫持。浏览器厂商们为了解决这个问题,提出了 HSTS Preload List 方案:内置一份可以定期更新的列表,对于列表中的域名,即使用户之前没有访问过,也会使用 HTTPS 协议。
目前这个 Preload List 由 Google Chrome 维护,Chrome、Firefox、Safari、IE 11 和 Microsoft Edge 都在使用。如果要想把自己的域名加进这个列表,首先需要满足以下条件:
- 拥有合法的证书 (如果使用 SHA- 1 证书,过期时间必须早于 2016 年);
- 将所有 HTTP 流量重定向到 HTTPS;
- 确保所有子域名都启用了 HTTPS;
- 输出 HSTS 响应头:
- max-age 不能低于 18 周 (10886400 秒);
- 必须指定 includeSubdomains 参数;
- 必须指定 preload 参数;
即便满足了上述所有条件,也不一定能进入 HSTS Preload List。
通过 Chrome 的 chrome://net-internals/#hsts 工具,可以查询某个网站是否在 Preload List 之中,还可以手动把某个域名加到本机 Preload List。
HSTS 并不是 HTTP 会话劫持的完美解决方案。用户首次访问某网站是不受 HSTS 保护的。这是因为首次访问时,浏览器还未收到 HSTS,所以仍有可能通过明文 HTTP 来访问。
如果用户通过 HTTP 访问 HSTS 保护的网站时,以下几种情况存在降级劫持可能:
- 以前从未访问过该网站
- 最近重新安装了其操作系统
- 最近重新安装了其浏览器
- 切换到新的浏览器
- 切换到一个新的设备,如:移动电话
- 删除浏览器的缓存
- 最近没访问过该站并且 max-age 过期了
解决这个问题目前有两种方案:
方案一:在浏览器预置 HSTS 域名列表,就是上面提到的 HSTS Preload List 方案。该域名列表被分发和硬编码到主流的 Web 浏览器。客户端访问此列表中的域名将主动的使用 HTTPS,并拒绝使用 HTTP 访问该站点。
方案二:将 HSTS 信息加入到域名系统记录中。但这需要保证 DNS 的安全性,也就是需要部署域名系统安全扩展。
其它可能存在的问题
由于 HSTS 会在一定时间后失效 (有效期由 max-age 指定),所以浏览器是否强制 HSTS 策略取决于当前系统时间。大部分操作系统经常通过网络时间协议更新系统时间,如 Ubuntu 每次连接网络时,OS X Lion 每隔 9 分钟会自动连接时间服务器。攻击者可以通过伪造 NTP 信息,设置错误时间来绕过 HSTS。
解决方法是认证 NTP 信息,或者禁止 NTP 大幅度增减时间。比如:Windows 8 每 7 天更新一次时间,并且要求每次 NTP 设置的时间与当前时间不得超过 15 小时。
目前主流浏览器都已经支持 HSTS 特性,具体可参考下面列表:
- Google Chrome 4 及以上版本
- Firefox 4 及以上版本
- Opera 12 及以上版本
- Safari 从 OS X Mavericks 起
- Internet Explorer 及以上版本
服务器开启 HSTS 的方法是:当客户端通过 HTTPS 发出请求时,在服务器返回的超文本传输协议响应头中包含 Strict-Transport-Security 字段。非加密传输时设置的 HSTS 字段无效。
最佳的部署方案是部署在离用户最近的位置,例如:架构有前端反向代理和后端 Web 服务器,在前端代理处配置 HSTS 是最好的,否则就需要在 Web 服务器层配置 HSTS。如果 Web 服务器不明确支持 HSTS,可以通过增加响应头的机制。如果其他方法都失败了,可以在应用程序层增加 HSTS。
HSTS 启用比较简单,只需在相应头中加上如下信息:
Strict-Transport-Security: max-age=63072000; includeSubdomains;preload;
Strict-Transport-Security 是 Header 字段名,max-age 代表 HSTS 在客户端的生效时间。includeSubdomains 表示对所有子域名生效。preload 是使用浏览器内置的域名列表。
HSTS 策略只能在 HTTPS 响应中进行设置,网站必须使用默认的 443 端口;必须使用域名,不能是 IP。因此需要把 HTTP 重定向到 HTTPS,如果明文响应中允许设置 HSTS 头,中间人攻击者就可以通过在普通站点中注入 HSTS 信息来执行 DoS 攻击。
$ vim /etc/apache2/sites-available/hi-linux.conf
# 开启 HSTS 需要启用 headers 模块
LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so
<VirtualHost *:80>
ServerName www.hi-linux.com
ServerAlias hi-linux.com
...
#将所有访问者重定向到 HTTPS, 解决 HSTS 首次访问问题。RedirectPermanent / https://www.hi-linux.com/
</VirtualHost>
<VirtualHost 0.0.0.0:443>
...
# 启用 HTTP 严格传输安全
Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
...
</VirtualHost>
重启 Apache 服务
$ service apche2 restart
$ vim /etc/nginx/conf.d/hi-linux.conf
server {
listen 443 ssl;
server_name www.hi-linux.com;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
...
}
server {
listen 80;
server_name www.hi-linux.com;
return 301 https://www.hi-linux.com$request_uri;
...
}
重启 Nginx 服务
$ service nginx restart
要在 IIS 上启用 HSTS 需要用到第三方模块。
设置完成了后,可以用 curl 命令验证下是否设置成功。如果出来的结果中含有 Strict-Transport-Security 的字段,那么说明设置成功了。
$ curl -I https://www.hi-linux.com
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 27 May 2017 03:52:19 GMT
Content-Type: text/html; charset=utf-8
...
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Frame-Options: deny
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
...
对于 HSTS 以及 HSTS Preload List,建议是只要不能确保永远提供 HTTPS 服务,就不要启用。因为一旦 HSTS 生效,之前的老用户在 max-age 过期前都会重定向到 HTTPS,造成网站不能正确访问。唯一的办法是换新域名。