Teambition 对于安全问题一直非常关注,前一阵子@严清老师郑重提出必须即时部署 CSP, 然后大家一起认真琢磨了一下,觉得确实是应该在社区中推广的安全策略,特别是 Teambition 的合作伙伴们=)。下文是严清撰写的实战经验,供大家参考。
最早接触 CSP 还是 AngularJS 的 ng-csp 指令,但实际上一直没有真正理解,直到两个月前看了 @xisigr 的这篇文章:《浏览器安全策略说之内容安全策略CSP》,才发现 CSP 的确是一个出色的 WEB 安全方案。
简而言之,内容安全策略(Content Security Policy,简称 CSP)是一种以可信白名单作机制,来限制网站中是否可以包含某来源的资源。资源类型细分为 Script、Style、Image、Font、Connect、Media、Object、Frame 等,可分别配置其来源。默认配置下不允许执行内联脚本和内联样式,也禁止执行 eval。
这里我以 Teambition 实际部署过程讲解 CSP 部署,让大家有个感性的认识。
CSP 规则讲解
我们先来看 Teambition 平台网页的一段 Response Headers:
content-encoding:gzip
content-security-policy:default-src https://*.teambition.com; script-src https://dn-st.oss.aliyuncs.com https://hm.baidu.com https://cdn.mxpnl.com https://www.google-analytics.com; style-src data: 'unsafe-inline' https://dn-st.oss.aliyuncs.com; img-src data: blob: https://*.teambition.com https://dn-st.oss.aliyuncs.com https://dn-st.qbox.me https://hm.baidu.com https://www.google-analytics.com; frame-src https:; font-src https://dn-st.oss.aliyuncs.com; connect-src https://*.teambition.com wss://*.teambition.com https://api.mixpanel.com
content-type:text/html; charset=utf-8
date:Sat, 02 Aug 2014 10:41:10 GMT
server:nginx
status:200 OK
teambition:wv=release
version:HTTP/1.1
其中 content-security-policy 的内容就是我们设定的 CSP 规则,Response Headers 中还有 x-content-security-policy 和x-webkit-csp,内容一样,是为了做浏览器兼容。其中的 CSP 规则详细解释如下:
default-src https://*.teambition.com;
default-src 默认规则,当相关资源未定义规则时,就使用默认规则,这里定义了只允许从 teambition.com 域及子域加载资源,并且必须是 https 安全链接。
script-src https://dn-st.oss.aliyuncs.com https://hm.baidu.com https://cdn.mxpnl.com https://www.google-analytics.com;
script-src JS 脚本规则,这里只允许从阿里云、百度、mixpanel、google 的指定域下加载脚本,其中阿里云是 teambition 使用的 CDN,后三者是因为要加载统计脚本。另外,CSP 规则默认不允许执行內联 JS 脚本、eval、Function 等。最大程度的限制了非法 JS 脚本的运行。我们部署 CSP 的主要工作就是去除源码中的內联脚本。
style-src data: 'unsafe-inline' https://dn-st.oss.aliyuncs.com;
style-src CSS 样式规则,这里只允许从阿里云的指定域下加载 CSS,并且允许 data 和內联 CSS,CSP 规则默认不允许內联 CSS,但实际场景中,很多 UI 控制都依赖內联 CSS,尤其是网页动画。
img-src data: blob: https://*.teambition.com https://dn-st.oss.aliyuncs.com https://dn-st.qbox.me https://hm.baidu.com https://www.google-analytics.com;
img-src img 规则,这里限制放得比较宽,其中还有百度和 google 的域,是因为他们的统计请求返回的是图片,如果禁了统计请求将失效。
frame-src https:;
frame-src frame 规则,这里目前允许所有 https 链接。实际上 teambition 目前只使用了 dropbox 的组件。今后根据情况再调整。
font-src https://dn-st.oss.aliyuncs.com;
font-src font 规则,同样只允许从阿里云加载。
connect-src https://*.teambition.com wss://*.teambition.com https://api.mixpanel.com
connect-src connect 规则,包括 XMLHttpRequest、WebSocket 等链接。teambition 的消息推送就用了 WebSocket 链接。
另外还有 Object、Media 等未定义的就使用 default-src 规则。也还有一些 CSP 特性,如 report-uri 攻击报告等,这里不再阐述。
CSP 后端部署
Teambition 使用的 node.js 服务器,部署相当简单,相关代码端如下:
if (config.CSP_HEADER) {
res.set({
'Content-Security-Policy': config.CSP_HEADER,
'X-Content-Security-Policy': config.CSP_HEADER,
'X-WebKit-CSP': config.CSP_HEADER
});
}
也就是判断 CSP 规则是否存在,存在就将其设置到网页头部。这里之所以有这个判断,是因为 teambition 存在开发环境、测试环境、线上环境和私有部署环境,不同环境配置了不同规则。
CSP 部署的前端工作
CSP 部署后端工作就是上面这段,无比简单,但真正的工作量却是在前端!
我们部署 CSP 最大目的就是禁止非法脚本运行,只允许从指定域加载脚本运行,其它域和內联脚本都应该禁止。我们的工作就是要去掉所有內联脚本。之前,Teambition 有两处用到了內联脚本。
一是全局变量 Teambition :
script
(function () {
window.teambition = {}
teambition.apiHost = '#{config.API_HOST}'
teambition.pushHost = '#{config.PUSH_HOST}'
teambition.uploadUrl = '#{config.FILE_HOST}'
teambition.loadingStart = Date.now()
})()
这是为了方便从服务器获取初始化配置信息,以及其它的前端构架内部的各种方便~。为了部署 CSP 只好忍痛去除全局变量:)。从服务器获取初始配置信息变成了这样:
script(id="teambition-global",
data-apihost="#{config.API_HOST}",
data-pushhost="#{config.PUSH_HOST}",
data-uploadurl="#{config.FILE_HOST}",
data-spiderhost="#{config.SPIDER_HOST}",
data-responsetime="#{timestamp}")
当然,从三万行代码中去除全局变量不是这么简单,细节这里就不叙述了,加上修 BUG,花了我一整天,改完之后确实是舒服多了。
二是统计脚本:
前端朋友应该都知道如何嵌入百度、google 等统计服务,没错,就是在网页中插入內联脚本。
Teambition 做了一个叫 GTA 的开源封装,将百度、google 和 mixpanel 三家统计分析服务融合到一起。去內联就是重构 GTA 模块,这里有一个要点,就是尽量提前(调用前)用 appKey 将它们初始化。具体重构就不细说了,请直接看代码。
最后,附送一个 CSP 规则生成器 http://cspisawesome.com/,Teambition 的 CSP 规则是在这里生成的。