×

Content-Security-Policy的实战应用

96
牧羊童鞋 595a1b60 08f6 4beb 998f 2bf55e230555
2017.10.11 21:35* 字数 1830

今天在浏览微信页面的时候,发现他的script标签上都有个once属性,好奇之下查阅了一番,发现这个属性是和一个http header Content-Security-Policy有关,这个header不看不知道,一看吓一跳啊,一把利器啊

1. 同源限制

首先我们要知道web浏览器为了安全都有会同源限制,什么是同源限制?就是来自 https://mybank.com 的代码应仅能访问 https://mybank.com 的数据,而绝不被允许访问 https://evil.example.com。同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据,比如cookie/locaStoragy/IndexDB就遵守同源限制。XMLHettpRequest也是存在同源限制,相信只要开发过web的同学在ajax获取数据时都遇到过这个问题。

同源限制可以一定程度上限制我们的用户信息不会被盗取,但是却没法防止我们的页面被插入不法分子的资源(js,img,css等),毕竟页面上带src的元素资源是不受同源限制的。这些页面上的牛皮鲜让人很讨厌,影响是极其恶劣的:会让我们的js监控误报、会影响用户体验、甚至隐私泄露,所以我们需要对src资源也作出一定的限制,这就得Content-Security-Policy来了

2. Content-Security-Policy(内容安全政策,下文简称为CSP)

  • 主要作用

了解一样东西,我们首先的知道他有啥用,没用不是浪费时间么,毕竟大家都在假装生活很忙的样子,作用呢主要有两点:

  1. 使用白名单的方式告诉客户端(浏览器)允许加载和不允许加载的资源。
  2. 向服务器举报这种强贴牛皮鲜广告的行为,以便做出更加针对性的措施予以绝杀。
  • 怎么用

我们知道了好处还是很犀利的啊,这么好的东西怎么玩?其实也很简单,前面说到了他其实就是一个http header嘛,所以我们只需要在返回html页面的同时加上个response header 就行了,后面的script-src代表是一个指令,指示浏览器你只能加载我屁股后面那些规则下的js代码,其他的都一律拒绝。

Content-Security-Policy: script-src 'self' https://apis.google.com

你还可以通过元标记的方式使用:

<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'">
  • 指令

前面说到script-src是一个指令,那就说明还有其他的指令罗,没有错,下面的都是指令,覆盖了web页面的所有资源

base-uri: 用于限制可在页面的 <base> 元素中显示的网址。
child-src: 用于列出适用于工作线程和嵌入的帧内容的网址。例如:child-src https://youtube.com 将启用来自 YouTube(而非其他来源)的嵌入视频。 使用此指令替代已弃用的 frame-src 指令。
connect-src: 用于限制可(通过 XHR、WebSockets 和 EventSource)连接的来源。
font-src: 用于指定可提供网页字体的来源。Google 的网页字体可通过 font-src https://themes.googleusercontent.com 启用。
form-action: 用于列出可从 <form> 标记提交的有效端点。
frame-ancestors: 用于指定可嵌入当前页面的来源。此指令适用于 <frame>、<iframe>、<embed> 和 <applet> 标记。此指令不能在 <meta> 标记中使用,并仅适用于非 HTML 资源。
frame-src: 已弃用。请改用 child-src。
img-src: 用于定义可从中加载图像的来源。
media-src: 用于限制允许传输视频和音频的来源。
object-src: 可对 Flash 和其他插件进行控制。
plugin-types: 用于限制页面可以调用的插件种类。
report-uri: 用于指定在违反内容安全政策时浏览器向其发送报告的网址。此指令不能用于 <meta> 标记,这就是举报电话
style-src: 是 script-src 版的样式表。
upgrade-insecure-requests: 指示 User Agent 将 HTTP 更改为 HTTPS,重写网址架构。 该指令适用于具有大量旧网址(需要重写)的网站。

这么多指令都要写?写起来不是很麻烦,不是的。你只需要写自己要求限制的指令就行,没写的都会默认没有限制。

你还可以通过指定一个 default-src 指令替换大部分指令的默认行为,也就说如果你写了default-src 指令,那其他没写的指令都会服从default-src 的规则。

  • 规则

规则主要是罗列一些你信任的域名,除此之外还有四个关键词:

none 表示不执行任何匹配。
self'表示与当前来源(而不是其子域)匹配。
unsafe-inline表示允许使用内联 JavaScript 和 CSS。
unsafe-eval 表示允许使用类似 eval 的 text-to-JavaScript 机制。

  • once属性

讲了这么多那和我一开始发现的那个script标签上的once属性有啥关系呢?首先说明不存在不正当*关系。主要是现代浏览器认为内联css和内联js都是应该被视为危险的行为,但是你总不能因为菜刀能杀人就不让百姓用菜刀了吧,所以开个口子吧。如果你在使用CSP策略的同时有确实需要使用内联css和js怎么办?用once+随机数的方式

<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
  //Some inline code I cant remove yet, but need to asap.
</script>

然后我们在CSP的白名单中加上

Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

这样你这段内联js就可以生效了

补充说明

CSP 1 在 Chrome、Safari 和 Firefox 中非常实用,但在 IE 10 中仅得到非常有限的支持。 您可以 在 canisue.com 上查看具体信息。CSP Level 2 在 Chrome 40 及更高版本中可用。 Twitter 和 Facebook 等大量网站已部署此标头(Twitter 的案例研究值得一读),并为您开始在自己的网站上进行部署制定了相应标准。

实战效果

我们加上CSP头部后,开启csp上报功能后发现,10分钟上报了几千条,这被强奸的厉害啊,所以加上这个头部就显得更加有必要了

遇到的坑

在应用CSP后,有用户反映访问我们的站点出现问题,我们发现用户获取到的meta头乱了,而且在里面发现了一个不是我们写的域名:local.adguard.com,一查发现adguard是款 vpn软件,他对我们meta头部进行修改,修改就算了,还修改错了。后面我们改成response header的方式,不使用meta了,发现他也会修改http的response header,但是没修改错,垃圾VPN害死人啊

参考文章:

同源限制 - 阮一峰
内容安全政策 - Google

技术
Web note ad 1