网络知识

Content-Security-Policy(CSP)详解

一、前言


作为前端工程师你真的了解 XSS 吗?越来越多超级应用基于 Web 构建,如何系统性攻防 XSS 尤为重要。微软安全工程师 1999 年底披露了 XSS,20多 年来 XSS 一直稳居 OWASP(开放 Web 应用程序安全 ) 报告前 TOP10。

跨域脚本攻击(XSS:Cross-Site Scripting)是最常见、危害最大的网页安全漏洞。XSS 攻击利用了浏览器对于从服务器所获取的内容的信任。恶意脚本在受害者的浏览器中得以运行,因为浏览器信任其内容来源,即使有的时候这些脚本并非来自于它本该来的地方。

为了防止它,要采取很多编程措施(比如大多数人都知道的转义、过滤HTML)。很多人提出,能不能根本上解决问题,即浏览器自动禁止外部注入恶意脚本?

这就是”内容安全策略”(Content Security Policy,缩写 CSP)的由来。

XSS(Cross-Site Scripting,跨站脚本攻击,简称XSS)攻击是一种利用网页应用程序的安全漏洞的攻击方式,攻击者通过在网页中注入恶意脚本代码,使其在用户的浏览器中执行。这些恶意脚本可以用来窃取用户的敏感信息、篡改网页内容或进行其他未经授权的操作。

有小伙伴可能会疑惑:”攻击者是怎么向网页注入恶意脚本的?”,我们看下面的例子:

有一个博客网站提供了评论功能,用户评论可以实时渲染到DOM中,但由于该博客并未做用户输入以及渲染到DOM时的数据校验,因此攻击者就可以评论如下内容:

当其他用户浏览评论时,浏览器会执行评论中的JavaScript代码。这个恶意脚本会窃取用户的Cookie数据,并将它发送到攻击者的服务器,攻击者可以在服务器上分析这些Cookie数据,可能用于进一步的攻击,如身份盗窃或会话劫持。

是不是很恐怖,当然聪明的朋友可能会说:”那我做好用户输入、客户端渲染的数据校验不就可以了吗?”。确实,做好用户输入的验证和客户端渲染数据的校验是防止XSS攻击的关键步骤之一,但不能完全依赖它来确保安全,因为XSS攻击可以非常隐蔽和复杂,比如下面这个例子:

有一个在线论坛网站用于用户发表和浏览帖子。它使用了一个开源的Markdown渲染库,该库用于将用户输入的Markdown文本转换为HTML以进行显示。该网站执行了严格的用户输入、数据渲染验证,以确保用户不能直接插入恶意脚本或HTML标签。但其依赖的Markdown渲染库被污染,在某个时刻被攻击者篡改,包含了以下代码:

当用户浏览Markdown时,浏览器会执行第三方库恶意代码。这个恶意脚本会窃取用户的Cookie数据,并将它发送到攻击者的服务器,攻击者可以在服务器上分析这些Cookie数据,可能用于进一步的攻击,如身份盗窃或会话劫持。

上述例子中该网站虽然对用户输入、数据渲染进行了严格的验证,但由于第三方库存在漏洞,导致恶意JavaScript代码被执行。当然,这可以通过尽量选择受信任的源(如官方仓库或社区维护的库)的依赖,以及定期审查依赖的安全风险来降低XSS攻击的风险

从上述两个例子我们可以看到想要防止XSS攻击是极度复杂且困难的,为了提供一种有效的方式来降低XSS攻击风险,W3C推出了一项安全措施——内容安全策略

二、CSP介绍


Content Security Policy (CSP) 是一种加固 Web 应用的安全性技术,通过在网站页面中设置 CSP Header 来限制页面中能够执行的脚本、样式、图片等资源。CSP 包括很多不同的策略,因此安全设置的具体值取决目标网站的需求和资源使用情况。一般而言,建议设置较为严格的 CSP,以避免 XSS、CSRF 等安全问题。例如,可以配置以下 CSP:

Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' https://example.com;

以上设置的 CSP 规则禁止所有来自第三方网站的资源,只允许本网站的资源加载。其中 script-src 只允许本网站和 example.com 的脚本加载,img-src 只允许本网站和 data: URI 的图片加载,style-src 只允许本网站和内联样式加载,font-src 只允许本网站和 example.com 的字体加载。请根据实际情况进行调整。

CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。

CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。

两种方法可以启用 CSP:

  • 设置 HTTP 的 Content-Security-Policy 头部字段;
  • 设置网页的<meta>标签。

2.1 使用HTTP的 Content-Security-Policy头部

在服务器端使用 HTTP的 Content-Security-Policy响应头部来指定安全策略。

Content-Security-Policy: policy: policy参数是一个包含了各种描述CSP策略指令的字符串。
示例1:

// index.js
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
   const html = fs.readFileSync('index.html', 'utf8');
   res.writeHead(200, {
       'Content-Type': 'text-html',
       'Content-Security-Policy': 'default-src http: https:' 
   });
   res.end(html);
}).listen(9000);

console.log('server listening on 9000');

以上代码使用原生nodejs起了个服务,然后设置响应头部'Content-Security-Policy': 'default-src http: https:'表示只能通过外联的方式来引用jscss,如果使用内联的将报错:

示例2

只能在指定的域下加载文件,这里表示只能从同域下加载,斜杠为转义符:'Content-Security-Policy': 'default-src \'self\''

<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>

打开控制台也可以看到请求在浏览器就已经被限制了:

如果要允许请求到这个域,添加进策略即可:

'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcss.com/' 

示例3

上面的策略是无法限制form表单的提交的,如下列表单,点击后直接跳到了百度页面:

 <form action="https://baidu.com">
        <button>click me</button>
    </form>

这时候就要设置form-action策略:

'Content-Security-Policy': 'default-src \'self\' https://cdn.bootcss.com/; form-action \'self\''

其中,default-src设置的是全局,如果只想限制js的请求,可以将default-src改为script-src
启用违例报告
默认情况下,违规报告并不会发送。为启用发送违规报告,需要指定 report-uri策略指令,并提供至少一个URI地址去递交报告:

'Content-Security-Policy': 'script-src \'self\'; report-uri /report'

这里的报告我们可以直接在浏览器看到,它会自动发送一个请求出去:

如果只想收集报告,但是不真正的去限制请求,那怎么办?除了Content-Security-Policy,还有一个Content-Security-Policy-Report-Only字段,表示不执行限制选项,只是记录违反限制的行为。将头部改为这个即可。

如果 Content-Security-Policy-Report-Only 标头和 Content-Security-Policy 同时出现在一个响应中,两个策略均有效。在 Content-Security-Policy 标头中指定的策略有强制性,而 Content-Security-Policy-Report-Only 中的策略仅产生报告而不具有强制性。

3.2 使用 meta 标签

以上规则可以在浏览器端设置,如:

<meta http-equiv="Content-Security-Policy" content="form-action 'self';">

但与服务器端设置有点不同的是,meta无法使用report,只能在服务器端设置:

三、CSP 实施策略


限制方式

default-src: 限制全局,默认所有都会使用这种规则。
script-src: 限制JavaScript的源地址。
style-src:限制层叠样式表文件源。
img-src:限制图片和图标的源地址。
font-src:定义了字体加载的有效来源。
connect-src:定义了请求、XMLHttpRequest、WebSocket 和 EventSource 的连接来源
child-src: 指定定义了 web workers 以及嵌套的浏览上下文(如<frame>和<iframe>)的源。
media-src:限制通过 <audio>、<video> 或 <track> 标签加载的媒体文件的源地址。
object-src:限制 <object> 或 <embed> 标签的源地址。
manifest-src:限制应用声明文件的源地址。
prefetch-src:指定预加载或预渲染的允许源地址。
worker-src:限制 Worker、SharedWorker 或 ServiceWorker 脚本源。
webrtc-src:指定WebRTC连接的合法源地址。

注⚠️:设置多个限制条件,后面的会覆盖前面的!

一个CSP头由多组CSP策略组成,中间由分号分隔。其中每一组策略包含一个策略指令和一个内容源列表。例如:

Content-Security-Policy: default-src 'self' www.baidu.com; script-src 'unsafe-inline'

什么是同源策略?

URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们是同源的。同源策略是浏览器上为安全性考虑实施的非常重要的安全策略。限制来自不同源的”document”,对当前”document”读取或设置某些属性。

在浏览器中,<script>、<img>、<link>、<frame>等标签都可加载跨域资源,而不受同源策略限制,这带”src”属性的标签加载时,实际上是由浏览器发起一次GET请求,不同于XMLHttpRequest,他们通过src属性加载的资源。但浏览器限制了JavaScript的权限,使其不能读、写其中返回的内容。跨域请求的安全基础是JavaScript无法修改请求对象的http头部。如果XMLHttpRequest能够跨域请求资源,可能导致敏感信息泄露,比如CSRF的token信息。

为什么使用同源策略?

一个重要原因就是对cookie的保护,cookie 中存着sessionID。如果已经登录网站,同时又去了任意其他网站,该网站有恶意JS代码。如果没有同源策略,那么这个网站就能通过js 访问document.cookie 得到用户关于的各个网站的sessionID。其中可能有银行网站,通过已经建立好的session连接进行攻击,这里有一个专有名词,CSRF,还有需要注意的是同源策略无法完全防御CSRF,这里需要服务端配合。