标签 HSTS 下的文章

支持 HSTS 比你想象的更容易。

由于服务器管理员没能正确设置 HTTP Strict Transport Security (HSTS),现今大量的 HTTPS 流量都能被轻松劫持。

HSTS 是一个被当前大多数 Web 浏览器所支持的 Web 安全策略,它可以帮助 Web 管理员保护他们的服务器和用户避免遭到 HTTPS 降级、中间人攻击和 Cookie 劫持等 HTTPS 攻击的危害。

95% 的 HTTPS 连接处于风险中

据最近的 Netcraft study 报告数据显示,当前多达 95% 的服务器所运行的 HTTPS 没有正确地设置 HSTS 或其它配置,以至于将 HTTPS 连接暴露于上述攻击风险之中。

更值得注意的是,Netcraft 在三年前进行的同样扫描,不正确配置的 HSTS 比例仍同现在一样。这表明 Web 管理员们并没有学会或被告知如何正确地设置 HSTS。

针对这些不安全的站点的最容易的攻击场景是 HTTPS 降级攻击,攻击者可以选择多种方式来迫使一个看起来安全的 HTTPS 连接根本不使用数据加密或使用更弱的算法,这样攻击者就可以进行数据窃取了。

据安全研究人员称,在这 95% 的没有正确设置 HSTS 的站点中,有很多银行和金融机构的网站。

你可以通过下面一行配置激活你的 HSTS

不需要费脑筋,你只需要将下述的一行配置添加到你的 HTTPS 服务器配置中即可实现 HSTS。

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

这一行可以让服务器告诉浏览器仅通过 HTTPS 连接来访问其内容,其策略有效期为长达一年的最大有效时间。

当上述配置生效后,即便用户在他的浏览器中输入 URL 时写了 “http://”前缀,浏览器仍将会自动切换“https://”。

更多关于 HTTPS 及 HSTS 的技术实现细节,请参阅以下文章:

自从关注了 HTTPS,Linux 中国就成了 HTTPS 的铁杆粉丝了,不但传播了很多 HTTPS 相关的文章,而且身体力行的将 http://linux.cn也[切换](/article-5361-1.html)到了https://linux.cn 。非但如此,还激进地配置了 HSTS 策略。

HSTS 是什么?

如果一个 web 服务器支持 HTTP 访问,并将其重定向到 HTTPS 访问的话,那么访问者在重定向前的初始会话是非加密的。举个例子,比如访问者输入 http://www.foo.com/ 或直接输入 foo.com 时。

这就给了中间人攻击的一个机会,重定向可能会被破坏,从而定向到一个恶意站点而不是应该访问的加密页面。

HTTP 严格传输安全(HSTS)功能使 Web 服务器告知浏览器绝不使用 HTTP 访问,在浏览器端自动将所有到该站点的 HTTP 访问替换为 HTTPS 访问。

服务器开启 HSTS 的方法是,当客户端通过 HTTPS 发出请求时,在服务器返回的 HTTP 响应头中包含 Strict-Transport-Security 字段。浏览器接收到这样的信息之后,在一定期限内对该网站的任何请求都会以 HTTPS 发起,而不会以 HTTP 发起并由服务器重定向到 HTTPS。

所以,我们早早就配置上了 HSTS 响应头了。

当前浏览器对 HSTS 的支持如下,可见现代浏览器已经绝大部分支持了:

浏览器对 HSTS 的支持

HSTS 预载入列表

但是,这就够了么?如果一个用户从来没有以 HTTPS 方式访问过我们的网站呢,那显然就没有机会得到 HSTS 响应头,从而还是有可能以 HTTP 的方式进行首次访问——虽然我们已经做了很多自动和强制的引导,但是总还稍有缺憾?

所以,追求完美人们,又提出了一个 HSTS 预载入列表 preload list (友情提示,请自备梯子)。

谷歌在浏览器安全方面总是走在前面,因此它维护了一个预载入列表给 Chrome 使用,这个列表会硬编码到 Chrome 浏览器中。后来,Firefox、Safari、 IE 11 和 Edge 一想,得了,咱也别自己弄一个了,都采用这个列表吧。所以,各大浏览器都支持同一个列表了。

如果要想把自己的域名加进这个列表,需要满足以下条件:

  • 有效的证书(如果使用 SHA-1 证书,必须是 2016 年前就会过期的);
  • 将所有 HTTP 流量重定向到 HTTPS;
  • 确保所有子域名都启用了 HTTPS,特别是 www 子域;
  • 输出 HSTS 响应头:

    • max-age 至少需要 18 周(10886400 秒),其实在 ssllabs 的测试里面建议更长一些;
    • 必须指定 includeSubdomains 参数;
    • 必须指定 preload 参数;

HSTS 响应头范例:

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

注意,提交的申请并不是自动处理的,人工处理也许需要一周到几周,你可以在这个表单提交查询看看是否列入了。或者在你的 Chrome 浏览器中访问 <chrome://net-internals/#hsts> 查询是否列入。一般来说,即便你已经列入到这个列表,但是依旧需要几个月才能逐渐从 Chrome 的 canary 更新通道更新到 dev 、beta 等通道,直到最后的 stable 通道。

郑重警示:如果你并不能确定你的网站从此以后一直使用 HTTPS,那还是不要加入的好。因为,加入后很难撤销,你可以要求撤销,但是这个数据重新更新到稳定版的 Chrome 同样需要几个月,而别的浏览器是如何处理这个撤销数据的,则无法保证。

换句话说,只有 HTTPS 骨灰粉才应该考虑加入。

我们就是 HTTPS 骨灰粉!

任性的站长当然是 HTTPS 骨灰粉了,虽然“一入宫门深似海”,将来 HTTPS 会不会和 L2TP、PPTP 一样被限制也未可知,但是这么有 BIG 的事情怎么可以不做呢?

上个月15号,我修改了符合申请条件的 HTST 响应头,然后提交了申请。

今天晚上突然心血来潮,想着看看是否批准下来了:

linux.cn 列入 HSTS 预载入列表了

哈哈,通过了!

然后马上去 Chrome 里面查询——没有……好吧,我用的是稳定版的 Chrome。去下载 canary 通道的 Chrome 看看。安装后马上查询一下:

列入了静态 STS 域名了

果然列入了 canary 通道的 Chrome 里面!第一次在这个新开封的浏览器里面访问 linux.cn,马上就变成绿色的 https://linux.cn

绿色的 HTTPS 图标

去查询一下 Chrome 的代码看看:

列入了 transport_security_state_static.json

从现在的数据看,列表中总共才 4630 个域名,其中 .cn 的才 8 个。怎么样,你要不要也加入进来?

编者按:前段时间,Google 报告说 CNNIC 签发的一个中级 CA 签发了一个伪造的 Google 证书,从而导致 Google 和 Mozilla 在其产品中取消了对 CNNIC 后继签发的证书信任。

本文就来讲述一下,这种伪造证书是如何被 Google 发现的,其技术机制是什么?如何在网站服务器上实现伪造证书防御和报告机制。

公钥固定(Public Key Pinning)是指一个证书链中必须包含一个白名单中的公钥,也就是说只有被列入白名单的证书签发机构(CA)才能为某个域名*.example.com签发证书,而不是你的浏览器中所存储的任何 CA 都可以为之签发。本文讲述了这种机制的背景知识,并提供了 Apache、 Lighttpd 和 NGINX 上的配置范例。

HTTP 公钥固定扩展

用你使用的银行做个例子,它一直使用 CA 公司 A 为其签发证书。但是在当前的证书体系下,CA 公司 B、CA 公司 C 和 NSA 的 CA 都能给你的银行创建证书,而你的浏览器会毫无疑虑的接受它们,因为这些公司都是你所信任的根 CA。

如果你的银行实现了 HPKP 并固定了它们的第一个中级证书(来自 CA 公司 A),那么浏览器将不会接受来自CA 公司 B 和 CA 公司 C 的证书,即便它们也有一个有效的信任链。HPKP 也允许你的浏览器将这种违例行为报告给该银行,以便银行知道被伪造证书攻击了。

HTTP 公钥固定扩展是一个从2011年开始开发的针对 HTTP 用户代理(即浏览器)的公钥固定标准。它由 Google 发起,甚至在 Chrome 中实现的固定机制可以使用一个人工维护的网站公钥固定列表,这个列表包含了固定的几个网站的公钥签名。(LCTT 译注:Chrome 和 FireFox 32 及以后版本都支持公钥固定机制,并使用内置的人工维护的公钥固定列表数据,这些数据随着浏览器软件的更新而更新,主要包括几个大型站点。目前还只有 Chrome 38+ 支持通过 HTTP 响应头传递公钥固定信息。)

以下是 HPKP 的几个功能简述:

  • HPKP 是在 HTTP 层面设置的,使用 Public-Key-Pins (PKP)响应头。
  • 该规则的保留周期通过 max-age 参数设置,单位是秒。
  • PKP 响应头只能用于正确的安全加密通讯里面。
  • 如果出现了多个这样的响应头,则只处理第一个。
  • 固定机制可以使用includeSubDomains参数扩展到子域。
  • 当接收到一个新的 PKP 响应头时,它会覆盖之前存储的公钥固定和元数据。
  • 公钥固定是用哈希算法生成的,其实是一个“主题公钥信息(SKPI)”指纹。

本文首先会介绍一些 HPKP 工作的原理,接下来我们会展示给你如何得到需要的指纹并配置到 web 服务器中。

SPKI 指纹 - 理论

以下摘自 Adam Langley 的帖子,我们哈希的是一个公钥,而不是证书:

通常来说,对证书进行哈希是一个显而易见的解决方案,但是其实这是错的。不能这样做的原因是 CA 证书可以不断重新签发:同一个公钥、主题名可以对应多个证书,而这些证书有不同的延展或失效时间。浏览器从下至上地在证书池中构建证书链时,另外一个版本的证书可能就替代匹配了你原本所期望的证书。

举个例子,StartSSL 有两个根证书:一个是以 SHA1 签名的,另外是一个是 SHA256。如果你希望固定住 StartSSL 作为你的 CA,那么你该使用哪个证书呢?你也许可以使用这两个,但是如果我不告诉你,你怎么会知道还有一个根证书呢?

相反地,对公钥进行哈希则不会有这个问题:

浏览器假定子证书是固定不动的:它总是证书链的起点。子证书所携带的签名一定是一个有效的签名,它来自其父证书给这个证书专门签发的。这就是说,父证书的公钥相对于子证书来说是固定的。所以可推论公钥链是固定的。

唯一的问题是你不能固定到一个交叉认证的根证书上。举个例子,GoDaddy 的根证书是 Valicert 签名的,这是为了让那些不能识别 GoDaddy 根证书的老客户可以信任其证书。然而,你不能固定到 Valicert 上,因为新的客户在证书链上发现了 GoDaddy 证书就会停止上溯(LCTT 译注:所以就找不到固定信息了)。

此外,我们是对 SubjectPublicKeyInfo(SPKI)进行哈希而不是对公钥位串。SPKI 包括了公钥类型、公钥自身及其相关参数。这很重要,因为如果对公钥进行哈希就有可能导致发生曲解攻击。对于一个 Diffie-Hellman 公钥而言:如果仅对公钥进行哈希,而不是对完整的 SPKI,那么攻击者可以使用同样的公钥而让客户端将其解释为其它组。同样地,这样也有可能强制将一个 RSA 密钥当成 DSA 密钥解释等等。

固定在哪里

你应该固定在什么地方?固定你自己的公钥并不是一个最好的办法。你的密钥也许会改变或撤销。你也许会使用多个证书,经常轮换证书的话密钥就改变了。也许由于服务器被入侵而撤销证书。

最容易但是不是太安全的方法是固定第一个中级 CA 证书。该证书是签名在你的网站证书之上的,所以签发该证书的 CA 的公钥肯定是在证书链上的。

采用这种方法你可以从同一个 CA 更新你的证书而不用担心固定信息不对。如果该 CA 发行了一个不同的根证书,也许你会遇到一些问题,对此并没有太好的解决方案。不过你可以通过如下做法来减轻这种问题的影响:

  • 从一个不同的 CA 申请一个备用的证书,并固定该备份。

RFC 里面说你至少需要做两个固定。一个是当前连接所使用的证书链上的,另外一个是备份的。

另外的固定是对备份公钥的,它可以是来自另外一个给你签发证书的不同 CA 的 SKPI 指纹。

在这个问题上还有一种更安全的方法,就是事先创建好至少三个独立的公钥(使用 OpenSSL,参见此页 了解 Javascript OpenSSL 命令生成器),并将其中两个备份到一个安全的地方,离线存储、不要放到网上。

为这三个证书创建 SPKI 指纹并固定它们,然后仅使用第一个作为当前的证书。当需要时,你可以使用备份密钥之一。不过你需要让 CA 给你做签名来生成证书对,这可能需要几天,依你的 CA 的工作情况而定。

对于 HPKP 来说这没有问题,因为我们使用的是公钥的 SPKI 哈希,而不是证书。失效或不同的 CA 签名链并不影响。

如果你按照上述方法生成并安全存储了至少三个独立的密钥,并固定它们,也可以防止你的 CA 撤销你的网站证书并签发一个假证书时出现问题。

SPKI 指纹

可以使用如下的 OpenSSL 命令来生成 SPKI 指纹,它出现在 RFC 草案 中:

openssl x509 -noout -in certificate.pem -pubkey | \
openssl asn1parse -noout -inform pem -out public.key;
openssl dgst -sha256 -binary public.key | openssl enc -base64

结果:

klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=

上面输入的 certificate.pem 文件是本站(https://raymii.org)的证书链中第一个证书。(在写本文时, COMODO RSA Domain Validation Secure Server CA, 序列号 2B:2E:6E:EA:D9:75:36:6C:14:8A:6E:DB:A3:7C:8C:07 )。

你也需要同样对你的另外两个备份公钥生成指纹。

故障

在写本文时(2015/1),唯一支持 HPKP 的浏览器(chrome)有一个严重的问题:Chrome 并不能够区分 HSTS 和 HPKP 响应头中的 max-age 和 includeSubdomains 参数。也就是说,如果你的 HSTS 和 HPKP 设置了不同的 max-age 和 includeSubdomains 参数,它们会互相搞乱。关于这个故障的更多信息参见:https://code.google.com/p/chromium/issues/detail?id=444511。感谢 Scott Helme(https://scotthelme.co.uk)发现并告诉我这个 Chromium 项目的问题。

Web 服务器配置

下面你可以看到三个主流 Web 服务器的配置方法。这只是一个 HTTP 响应头,绝大多数 Web 服务器都可以设置它。它只需要设置到 HTTPS 网站上。

下面的例子固定到 COMODO RSA Domain Validation Secure Server CA 及备份的 Comodo PositiveSSL CA 上,30天失效期,包括所有的子域。

Apache

编辑你的 Apache 配置文件(如 /etc/apache2/sites-enabled/website.conf 或 /etc/apache2/httpd.conf),并添加下列行到你的 VirtualHost 中:

# 如需要,载入 headers 模块。
LoadModule headers_module modules/mod_headers.so

Header set Public-Key-Pins "pin-sha256=\"klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=\"; pin-sha256=\"633lt352PKRXbOwf4xSEa1M517scpD3l5f79xMD9r9Q=\"; max-age=2592000; includeSubDomains"

Lighttpd

Lighttpd 更简单一些,将下列行添加到你的 Lighttpd 配置文件(如 /etc/lighttpd/lighttpd.conf):

server.modules += ( "mod_setenv" )
$HTTP["scheme"] == "https" {
    setenv.add-response-header  = ( "Public-Key-Pins" => "pin-sha256=\"klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=\"; pin-sha256=\"633lt352PKRXbOwf4xSEa1M517scpD3l5f79xMD9r9Q=\"; max-age=2592000; includeSubDomains")
}

NGINX

NGINX 的配置更简短。添加以下行到你的 HTTPS 配置的 server 块中:

add_header Public-Key-Pins 'pin-sha256="klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY="; pin-sha256="633lt352PKRXbOwf4xSEa1M517scpD3l5f79xMD9r9Q="; max-age=2592000; includeSubDomains';

报告功能

HPKP 报告功能允许浏览器报告任何违例给你。

如果你在响应头中添加了附加的 report-uri="http://example.org/hpkp-report" 参数,并用该 URI 处理接收到的数据的话,客户端会在发现违例时发送报告给你。这个报告是以 POST 方式发送到你指定的 report-uri 上,并以类似下面的 JSON 格式:

{
    "date-time": "2014-12-26T11:52:10Z",
    "hostname": "www.example.org",
    "port": 443,
    "effective-expiration-date": "2014-12-31T12:59:59",
    "include-subdomains": true,
    "served-certificate-chain": [
        "-----BEGINCERTIFICATE-----\nMIIAuyg[...]tqU0CkVDNx\n-----ENDCERTIFICATE-----"
    ],
    "validated-certificate-chain": [
        "-----BEGINCERTIFICATE-----\nEBDCCygAwIBA[...]PX4WecNx\n-----ENDCERTIFICATE-----"
    ],
    "known-pins": [
        "pin-sha256=\"dUezRu9zOECb901Md727xWltNsj0e6qzGk\"",
        "pin-sha256=\"E9CqVKB9+xZ9INDbd+2eRQozqbQ2yXLYc\""
    ]
}

非强制,只报告

HPKP 也可以设置为非强制的,可以使用 Public-Key-Pins-Report-Only 来只发送违例报告给你。

这样可以让你在网站不可访问或 HPKP 配置不正确时不固定,之后你可以将这个响应头改为 Public-Key-Pins 来强制固定。


via: https://raymii.org/s/articles/HTTP_Public_Key_Pinning_Extension_HPKP.html

作者:Remy van Elst 译者:wxy 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

HTTP 严格传输安全(HSTS)是一种安全功能,web 服务器通过它来告诉浏览器仅用 HTTPS 来与之通讯,而不是使用 HTTP。本文会说明如何在 Apache2、Nginx 和 Lighttpd 上如何启用 HSTS。在主流的 web 服务器上测试通过: Nginx 1.1.19、 Lighttpd 1.4.28 和 Apache 2.2.22 ,环境为 Ubuntu 12.04、 Debian 6 & 7 和 CentOS 6,只需要调整部分参数就可以工作在其它的发行版上。

什么是 HTTP 严格传输安全?

引用自 Mozilla Developer Network

如果一个 web 服务器支持 HTTP 访问,并将其重定向到 HTTPS 访问的话,那么访问者在重定向前的初始会话是非加密的。举个例子,比如访问者输入 http://www.foo.com/ 或直接输入 foo.com 时。

这就给了中间人攻击的一个机会,重定向可能会被破坏,从而定向到一个恶意站点而不是应该访问的加密页面。

HTTP 严格传输安全(HSTS)功能使 Web 服务器告知浏览器绝不使用 HTTP 访问,在浏览器端自动将所有到该站点的 HTTP 访问替换为 HTTPS 访问。

以下引自维基百科

HSTS 可以用来抵御 SSL 剥离攻击。SSL 剥离攻击是中间人攻击的一种,由 Moxie Marlinspike 于2009年发明。他在当年的黑帽大会上发表的题为 “New Tricks For Defeating SSL In Practice” 的演讲中将这种攻击方式公开。SSL剥离的实施方法是阻止浏览器与服务器创建HTTPS连接。它的前提是用户很少直接在地址栏输入https://,用户总是通过点击链接或3xx重定向,从HTTP页面进入HTTPS页面。所以攻击者可以在用户访问HTTP页面时替换所有https://开头的链接为http://,达到阻止HTTPS的目的。

HSTS可以很大程度上解决SSL剥离攻击,因为只要浏览器曾经与服务器创建过一次安全连接,之后浏览器会强制使用HTTPS,即使链接被换成了HTTP。

另外,如果中间人使用自己的自签名证书来进行攻击,浏览器会给出警告,但是许多用户会忽略警告。HSTS解决了这一问题,一旦服务器发送了HSTS字段,用户将不再允许忽略警告。

场景举例:

当你通过一个无线路由器的免费 WiFi 访问你的网银时,很不幸的,这个免费 WiFi 也许就是由黑客的笔记本所提供的,他们会劫持你的原始请求,并将其重定向到克隆的网银站点,然后,你的所有的隐私数据都曝光在黑客眼下。

严格传输安全可以解决这个问题。如果你之前使用 HTTPS 访问过你的网银,而且网银的站点支持 HSTS,那么你的浏览器就知道应该只使用 HTTPS,无论你是否输入了 HTTPS。这样就防范了中间人劫持攻击。

注意,如果你之前没有使用 HTTPS 访问过该站点,那么 HSTS 是不奏效的。网站需要通过 HTTPS 协议告诉你的浏览器它支持 HSTS。

服务器开启 HSTS 的方法是,当客户端通过HTTPS发出请求时,在服务器返回的 HTTP 响应头中包含 Strict-Transport-Security 字段。非加密传输时设置的HSTS字段无效。

在 Apache2 中设置 HSTS

编辑你的 apache 配置文件(如 /etc/apache2/sites-enabled/website.conf/etc/apache2/httpd.conf ),并加以下行到你的 HTTPS VirtualHost:

# Optionally load the headers module:
LoadModule headers_module modules/mod_headers.so

<VirtualHost 67.89.123.45:443>
    Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
</VirtualHost>

现在你的 web 站点在每次访问时都会发送该请求头,失效时间是两年(秒数)。这个失效时间每次都会设置为两年后,所以,明天你访问时,它会设置为明天的两年后。

你只能在 HTTPS 虚拟机中设置这个头,而不能设置在 HTTP 虚拟机中。

要将你的访问者重定向到对应 HTTPS 站点,可使用如下设置:

<VirtualHost *:80>
  [...]
  ServerName example.com
  Redirect permanent / https://example.com/
</VirtualHost>

如果仅仅是做重定向的话,甚至不需要设置 DocumentRoot。

你也可以使用 mod\_rewrite 来做重定向,但是上述的方式更简单更安全。不过,mod\_rewrite 可以重定向页面到对应的 HTTPS 页面,而上述配置则只重定向到“/”:

<VirtualHost *:80>
  [...]
  <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
  </IfModule>
</VirtualHost>

不要忘记重启 Apache。

Lighttpd

对于 lighttpd 来说很简单,将下述配置增加到你的 Lighttpd 配置文件(例如:/etc/lighttpd/lighttpd.conf):

server.modules += ( "mod_setenv" )
$HTTP["scheme"] == "https" {
    setenv.add-response-header  = ( "Strict-Transport-Security" => "max-age=63072000; includeSubdomains; preload")
}

重启 Lighttpd。失效时间也是两年。

Nginx

Nginx 甚至更简单,将下述行添加到你的 HTTPS 配置的 server 块中:

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

不要忘记重启 Nginx。