缘起:启用HTTPS也不够安全 有不少网站只通过HTTPS对外提供服务,但用户在访问某个网站的时候,在浏览器里却往往直接输入网站域名(例如),而不是完整的URL(例如
图1:服务器和浏览器在背后帮用户做了很多工作 简单来讲就是,浏览器向网站发起一次HTTP请求,在得到一个重定向响应后,发起一次HTTPS请求并得到最终的响应内容。所有的这一切对用户而言是完全透明的,所以在用户看来,在浏览器里直接输入域名却依然可以用HTTPS协议和网站进行安全的通信,是个不错的用户体验。 一切看上去都是那么的完美,但其实不然,由于在建立起HTTPS连接之前存在一次明文的HTTP请求和重定向(上图中的第1、2步),使得攻击者可以以中间人的方式劫持这次请求,从而进行后续的攻击,例如窃听数据、篡改请求和响应、跳转到钓鱼网站等。 以劫持请求并跳转到钓鱼网站为例,其大致做法如下图所示:
图2:劫持HTTP请求,阻止HTTPS连接,并进行钓鱼攻击 第1步:浏览器发起一次明文HTTP请求,但实际上会被攻击者拦截下来 第2步:攻击者作为代理,把当前请求转发给钓鱼网站 第3步:钓鱼网站返回假冒的网页内容 第4步:攻击者把假冒的网页内容返回给浏览器 这个攻击的精妙之处在于,攻击者直接劫持了HTTP请求,并返回了内容给浏览器,根本不给浏览器同真实网站建立HTTPS连接的机会,因此浏览器会误以为真实网站通过HTTP对外提供服务,自然也就不会向用户报告当前的连接不安全。于是乎攻击者几乎可以神不知鬼不觉的对请求和响应动手脚。 解决之道:使用HSTS 既然建立HTTPS连接之前的这一次HTTP明文请求和重定向有可能被攻击者劫持,那么解决这一问题的思路自然就变成了如何避免出现这样的HTTP请求。我们期望的浏览器行为是,当用户让浏览器发起HTTP请求的时候,浏览器将其转换为HTTPS请求,直接略过上述的HTTP请求和重定向,从而使得中间人攻击失效,以规避风险。其大致流程如下:
图3:略过HTTP请求和重定向,直接发送HTTPS请求 第1步:用户在浏览器地址栏里输入网站域名,浏览器得知该域名应该使用HTTPS进行通信 第2步:浏览器直接向网站发起HTTPS请求 第3步:网站返回相应的内容 那么问题来了,浏览器是如何做到这一点的呢?它怎么知道哪个网站应该发HTTPS请求,哪个网站应该用HTTP请求呢?此时就该HSTS闪亮登场了。 HSTS HSTS的全称是HTTP Strict-Transport-Security,它是一个Web安全策略机制(web security policy mechanism)。 HSTS最早于2015年被纳入到ThoughtWorks技术雷达,并且在2016年的最新一期技术雷达里,它直接从“评估(Trial)”阶段进入到了“采用(Adopt)“阶段,这意味着ThoughtWorks强烈主张业界积极采用这项安全防御措施,并且ThoughtWorks已经将其应用于自己的项目。 HSTS最为核心的是一个HTTP响应头(HTTP Response Header)。正是它可以让浏览器得知,在接下来的一段时间内,当前域名只能通过HTTPS进行访问,并且在浏览器发现当前连接不安全的情况下,强制拒绝用户的后续访问要求。 HSTS Header的语法如下: Strict-Transport-Security: <max-age=>[; includeSubDomains][; preload] 其中: max-age是必选参数,是一个以秒为单位的数值,它代表着HSTS Header的过期时间,通常设置为1年,即31536000秒。 includeSubDomains是可选参数,如果包含它,则意味着当前域名及其子域名均开启HSTS保护。 preload是可选参数,只有当你申请将自己的域名加入到浏览器内置列表的时候才需要使用到它。关于浏览器内置列表,下文有详细介绍。 让浏览器直接发起HTTPS请求只要在服务器返回给浏览器的响应头中,增加Strict-Transport-Security这个HTTP Header(下文简称HSTS Header),例如: Strict-Transport-Security: max-age= 31536000; includeSubDomains 就可以告诉浏览器,在接下来的31536000秒(1年)内,对于当前域名及其子域名的后续通信应该强制性的只使用HTTPS,直到超过有效期为止。 完整的流程如下图所示: 图4:完整的HSTS流程 (责任编辑:本港台直播) |