2020-07-22
简介
上一篇博客介绍了XSS攻击(Cross Site Scripting)的原理。常规过滤脚本等防范方式,不仅麻烦,而且消耗性能,易攻难守,容易出纰漏。那干嘛不制定一个白名单,只有在白名单的内容才能正常执行呢?这个白名单就是Content Security Policy(内容安全策略),简称CSP。网站通过CSP声明了哪些来源的资源是可信的,就算黑客成功注入了XSS脚本,只要不在CSP声明的范围内,浏览器就不会加载执行,而是抛出错误。
通常情况下,网站通过http响应header来配置CSP。只需要增加像这样一行:
content-security-policy: default-src 'self';
也可以通过HTML的<meta>来配置,比如:
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">
两者选一种即可。如果两者都设置了,浏览器会校验两次,重复抛出错误。
指令规则
CSP由一系列的策略指令组成:JS脚本是一个策略指令,CSS样式又是一个策略指令,图片也是一个策略指令,等等。每个指令都描述了这类型资源的白名单策略,这一系列的策略指令,共同构成了网站的CSP策略。每个策略指令需要大概长这样:
script-src 'self' blog.oonne.com;
如上所示,红色部分表示这个策略对应的资源类型,比如“script-src”表示这个策略是针对脚本代码;蓝色部分表示白名单列表,列表的每一项空格隔开。合起来,上面这个策略就表示“本网站仅允许加载执行:来自本域名、blog.oonne.com的脚本代码”。
策略类型
常用策略类型有以下几种:
default-src: 缺省值。没有单独声明策略的资源,都将使用此配置,建议必填;
script-src: JavaScript脚本;
style-src: CSS样式;
img-src: 图片资源;
font-src: 字体资源;
media-src: 媒体资源(音视频),如<audio>、<video>标签等;
object-src: 插件,如<object>、<embed>、<applet>标签等;
connect-src: 能通过脚本加载的URL。包括XMLHttpRequest (AJAX)、WebSocket、fetch()、EventSource等;
child-src: 框架,如<frame>、<iframe>等(老版本用的是frame-src,现已弃用并切到child-src);
frame-ancestors: 嵌入的外部资源,如<frame>、<iframe>、<embed>、<applet>等;
form-action: 表单,<form>标签;
worker-src: worker脚本;
manifest-src: manifest 文件;
base-uri: HTML里src内容;
prefetch-src: 限制HTML可以导航到的地址,比如表单提交地址等。
策略值
对应上述类型,策略值有以下几种:
值 | 说明 |
* | 通配符,允许任何来源 |
'none' | 禁止任何来源 |
'self' | 允许符合同源策略的资源 |
域名 (如blog.oonne.com,或通配域名 *.oonne.com) | 允许来自此域名的资源 |
data: | 允许dataUrl,通常是经过Base64编码的资源 |
https: | 允许https协议的资源 |
'unsafe-inline' | 允许内联资源,如<script>标签 |
'unsafe-eval' | 允许eval()执行内容 |
'unsafe-hashes' | 允许事件里的内容,如onclick等 |
'strict-dynamic' | 允许动态创建的标签,如document.createElement('script') |
'nonce-' | 允许带有指定nonce值的标签 |
'sha256-' | 允许指定Hash值的内容 |
以上一些特殊值必须在单引号中才能生效。
最后提到的'nonce-',是个标识。举个例子,如果定义了 script-src 'nonce-oonne',那么只有带有这个标识的<script>标签允许执行,如:
<script nonce=oonne> // 可以执行的代码 </script>
nonce可以由服务端生成网页的时候,顺便随机生成,这样可有效避免攻击。
'sha256-'是把你要执行的代码计算出hash值,这样就能定义你执行的代码,避免被篡改。比如,需要执行以下代码:
<script>doSomething();</script>
把“doSomething()”用openssl计算sha256的base64,并在CSP里定义 script-src 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc=' 即可。
策略指令
把上述策略类型和策略指组合起来,就是一个网站的CSP指令了。举个例子,假设一个网站的CSP设置为:
Content-Security-Policy: default-src 'self'; script-src 'self' blog.oonne.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:;
表示:允许来自当前域名以及blog.oonne.com的脚本;允许来自当前域名以及页面内<style>标签的CSS样式;允许来自当前域名以及dataUrl的图片;其他资源仅允许来自当前域名。
违例报告
除了防范XSS攻击,CSP还可以记录攻击记录。举例,可给CSP策略中新增以下内容:
report-uri /csp-reports;
这样,一旦浏览器判断到CSP策略以外的资源,怀疑遭到XSS攻击,就会向/csp-reports这个路径发起一个POST方法的http请求。内容是一个JSON对象,含有以下字段:
document-uri: 发生违规的文档的URI。
referrer: 发生违规的文档引用(地址)。
blocked-uri: 被CSP阻止的资源URI。
violated-directive: 违反的策略名称。
original-policy: CSP声明的原始策略。
这样就能根据收到的违例报告,判断是自己的CSP没配置对导致本站资源报错,还是有人企图对网站进行XSS攻击。
为了降低CSP的部署成本,还提供了“报告模式”(report-only)。只需要把想测试的CSP策略,放到
Content-Security-Policy-Report-Only字段下。违反策略的资源不会被拦截,但会发送违规报告。这样就能放心进行测试和部署了。
总结
合理规划网站资源储存加载路径,配置严格的CSP,可以有效防止XSS攻击。
一些古董级别的浏览器(主要是IE)依然不支持CSP。如果要照顾这些用户,除了设置X-XSS-Protection等旧浏览器支持的方案外,其他常规安全措施也还得做。
参考文献
本文未经许可禁止转载,如需转载请联系 JAY@oonne.com