南琴浪博客

常用的访问安全 HTTP 响应头

10/15/2017

Webserver 给予一个 HTTP Respose 时,响应中通常包括有一些响应头信息。而现在的浏览器有很多均能够提供一些安全相关的响应头。使用这些响应头一般不需要修改项目代码,只需要修改 Web 服务端的相关配置即可。本文会介绍一些这样的响应头,并以 Nginx 为例来启用这些响应头以加强站点安全。

HTTP-Strict-Transport-Security

概述

HTTP 严格传输安全(HTTP Strict Transport Security),一般简称为 HSTS。是一套由互联网工程任务组发布的互联网安全策略机制。网站可以选择使用 HSTS 策略,让浏览器强制使用 HTTPS 与网站进行通信,以减少会话劫持风险。

毫无疑问,HTTPS 相比 HTTP 有更好的安全性。而当用户主动输入 HTTP 地址,或者某些 HTTPS 网站也提供 HTTP 访问方式时,用户最终会以 HTTP 访问网站,降低了该访问的安全性。

许多网站使用 301 等方式保证用户使用 HTTPS 访问网站。而 HSTS 出现后,浏览器会直接把你的访问请求更改为 HTTPS 方式

要启用 HTTPS 策略,需要在你站点的响应头中加入这个头:

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

其中 includeSubDomains 含义是指定 HSTS 响应头同时作用于子域名。

配置

如果你使用 Nginx 作为 webserver,那么你需要在 Nginx 中这样配置:

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

启用 HSTS 后,当用户的浏览器 第一次以 HTTPS 方式访问网站,因为 HSTS 这个响应头的存在,浏览器会保存这条 HSTS 记录。这样以后,即使用户在地址栏输入 HTTP 地址,浏览器也会自动更换成 HTTPS 请求。这样节省一次来自远程服务器的 301,也能保证浏览器只会发送 HTTPS 请求,在请求的一开始就能避免劫持。

而 HSTS 的缺陷是,这个策略要求用户曾经以 HTTPS 方式访问过目标网站,也就是要求浏览器要有 HSTS 记录。那么如果用户第一次访问是 HTTP,以后还是 HTTP,然而 HTTPS 一次也不用,那岂不是一直不能用上 HSTS 了?

如果想避免这种尴尬的话,可以给站点加上 301 跳转,达到使用户使用 HTTPS 访问过一次的目的。以后该用户再第二次访问时,就因为 HSTS 的存在而直接跳到 HTTPS 了,节约了 301 过程。

HSTS 现已有广泛的浏览器支持,Chrome 内置了一个 HSTS 预加载列表(HSTS Preloading List),里面包括了在 Chrome 中已经预存过 HSTS 记录的站点。这个列表随着每次 Chrome 的浏览器版本升级而更新。你可以在地址栏输入 chrome://net-internals/#hsts 进入 HSTS 管理界面,在这个页面,你可以 查询/增加/删除 HSTS 记录。

若想把自己的网站加入 HSTS Preloading List,你需要前往 https://hstspreload.org/ 进行申请。

HTTP Public Key Pinning

概述

HTTP 公钥固定(又称 HTTP 公钥钉扎,英语:HTTP Public Key Pinning,缩写 HPKP),是 HTTPS 网站防止攻击者使用 CA 错误签发的证书进行中间人攻击的一种安全机制,用于预防诸如攻击者入侵 CA 偷发证书、浏览器信任的 CA 签发伪造证书等情况。采用该机制后,网站服务器会提供一个公钥 Hash 列表,客户端在后续通讯中将只接受该列表上的一个或多个公钥。

受信任的 CA(证书颁发机构)有好几百个,这些 CA 可能成为网站身份认证过程中的一个被利用的缺口。现有的证书信任链机制最大的问题是,任何一家受信任的 CA 都可以签发任意网站的站点证书,这些证书浏览器都会认为是合法的。这一特性可能会被攻击者利用。

这时 HPKP 出现了,HPKP 给予我们主动选择信任 CA 的权利。它的工作原理是通过 HPKP 记录(HTTP header 或 meta 标签)告诉浏览器当前网站的证书指纹,在未来的 HPKP 有效时间内,浏览器再次访问该网站时必须核对证书指纹,如果跟之前记录的值不匹配,即便站点证书自身是合法的,浏览器也会判定必须断开连接。

顺便补充一下,说到 主动选择信任 CA 这个事,DNS CAA 策略也能起到这个作用。关于 CAA 可以看我的 这篇文章

HPKP 官方文档位于 RFC7469

要启用 HPKP 策略,在你站点的响应头中加入这个头:

Public-Key-Pins: pin-sha256="==base64=="; pin-sha256="==base64=="; max-age=${expiretime}; (includeSubdomains;) (report-uri="${report-uri}")

其中:

  • pin-sha256 证书指纹,是一串 base64 加密。指纹至少需要填写两份,且可以写入更多个(当然得是有效的)
  • max-age 过期时间。单位是秒。最大数值 31536000 秒,即一年
  • includeSubdomains 可选项。指定 HPKP 同时对子域生效
  • report-uri 可选项。指定验证失败时的上报地址

上文提到,证书指纹至少需要填写两份。为了验证证书合法性,Webserver 出示的指纹必须必须匹配当前证书。举个例子,本博客的 SSL 证书为二级证书(Comodo Positive SSL 提供一级中间和两级根,不过 AddTrust 已经死人一个,故只考虑为一个根),本站可以选择站点证书和中间证书、站点证书中的至少一个生成指纹,这样一来我就得到了两份指纹。

其中:

  1. 使用站点证书生成指纹具有最高安全性,因为这个指纹唯一对应你的证书。缺点是证书重签后指纹发生变化,需要再次生成新的指纹,也就是你需要更换一次 HPKP 记录的指纹
  2. 使用根证书生成指纹具有最低安全性,因为每个根证书都对应着许多中间证书,攻击者只要盗取其中一个 CA 的私钥,就能私自签出能被 HPKP 判定信任的站点证书
  3. 而使用中间证书则没有很明确的优点或缺点

综上考虑,一般推荐使用 站点证书 + 中间证书 生成指纹的组合。

关于 站点证书、中间证书、根证书 三者的区别,我举个例子以简单区分:

证书类型颁发者颁发给
根证书COMODO ECC Certification AuthorityCOMODO ECC Certification Authority
中间证书COMODO ECC Certification AuthorityCOMODO ECC Domain Validation Secure Server CA
站点证书COMODO ECC Domain Validation Secure Server CAsometimesnaive.org

配置

要启用 HPKP 需要指定证书指纹,那么第一步就需要生成证书指纹:

1.检查证书类型

openssl x509 -in intermediate.cer -noout -subject

# 通过返回值判断是哪种证书类型

# 下面这条返回值的 CN(common name)可以看出这是中间证书
subject= /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3

# 可以判断这个是我的站点证书
subject= /CN=sometimesnaive.org

2.生成 Public Key

openssl x509 -in intermediate.cer -noout -pubkey | openssl asn1parse -noout -inform cer -out intermediate.key

# 执行这条后,在当前目前生成 intermediate.key
# 这条命令运行成功后,ssh 不会有信息输出

3.用 Public Key 来生成 base64 指纹

openssl dgst -sha256 -binary intermediate.key | openssl enc -base64

# 返回值就是你需要的证书指纹
ISP2bKPWC4I2OyKnm2Uy9LISXHnu5GFOoR3rDExlriA=

重复上面过程,生成至少两组指纹后,完成响应头的填写,配置到 Web 端。

以 Nginx 为例,结果类似这行:

add_header Public-Key-Pins    'pin-sha256="RjB0Ads2Pfeac6jCLz/BFDG0DIBCBBh2LKO4VhXreiA="; pin-sha256="hLFDSCBycE59B1QJ7eGHgmg1B6LKcycdxrILzVl9nFj="; max-age=31536000; includeSubDomains';

可以在 ssllabs 测试 HPKP 是否成功启用。

需要说明的是,虽然 HPKP 有预防中间人的作用,但其也有较大的副作用,攻击者可以利用 HPKP 进行拒绝访问攻击。并且,再举个最简单的例子,例如你原本一直使用 Let’s Encrypt 证书,而有一天却换到了 comodo 证书,那访客就不能打开你网站了(伪-拒绝访问攻击),除非你的访客禁用浏览器的 HPKP 策略。并且 Chrome 已计划于 5 月份的 67 版本移除 HPKP 支持。不再推荐网站使用 HPKP 策略。

X-Frame-Options

X-Frame-Options 响应头用于防范点击劫持(Click Jacking)。

响应头格式为:

x-frame-options: ${value}

可填入的 value 有三种:

  • allow-from ${domain} 不允许被指定域名以外的页面嵌入
  • sameorigin 不允许被站点自身域名以外的页面嵌入
  • deny 不允许被任何页面嵌入

由于嵌入的页面不会加载,这就减少了点击劫持的发生。

在 Nginx 中的配置:

add_header X-Frame-Options deny;

X-Content-Type-Options

互联网上的资源多种多样,在 web 浏览方面,一般浏览器会根据响应头的 Content-Type 字段分辨资源的类型。例如,”text/html” 表示 html 文件,”text/css” 表示 CSS 样式文件,”application/javascript” 表示 js 代码。但有时,会出现 Content-Type 未定义或无效值的资源,这时浏览器会调用 MIME-sniffing 来猜测该资源的类型。攻击者利用浏览器的这种猜测,例如可以让原本应解析为图片的请求被解析为恶意 JavaScript 脚本,被攻击者成功入侵。

为了防范这类攻击,需要禁用浏览器的资源类型猜测。

通过返回下面这个响应头:

X-Content-Type-Options: nosniff

其中,这个响应头的值只能是 nosniff

在 Nginx 中的配置:

add_header X-Content-Type-Options nosniff;

X-XSS-Protection

顾名思义,这个响应头用于防范 XSS 攻击。

该响应头已具有广泛浏览器支持,并且主流浏览器基本默认开启 XSS 保护。倒是用这个头可以关闭它。虽然一般不建议这样做。

响应头格式为:

X-Xss-Protection: ${value}

可填入的值有三种:

  • 0 禁用 XSS 保护
  • 1 启用 XSS 保护
  • 1; mode=block 启用 XSS 保护,并规定在检查到 XSS 攻击时停止页面渲染

在 Nginx 中的配置:

add_header X-Xss-Protection "1; mode=block" ;

X-Content-Security-Policy

这个头用于定义页面中哪些资源可以加载,以减少 XSS 的发生。

关于 CSP(内容安全策略)的更多,可以看我的另一篇帖子 Content Security Policy 概述