你尚未登录,仅允许查看本站部分内容。请登录使用邀请码注册
Coocier

Chrome Extension 中的 CSP(Content Security Policy) 开发小记 2个回复 专栏 @ Javascript

Coocier 发布于 9 月前

Chrome Extension CSP 开发小记

标签(空格分隔): chrome-extension web开发 CSP


在进行Chrome拓展程序开发的时候,我们经常会遇到需要加载第三方库的情况,常见的如Jquery/Bootstrap库等,然而Chrome Extension的开发与一般网页不同,当我们在页面中加入如下代码时

<script src="https://code.jquery.com/jquery-2.2.2.min.js"></script>

我们希望看到的是拓展程序能够顺利加载jquery-2.2.2.min.js这个库文件,但是,当我们打开该拓展程序的chrome console控制台,发现出现了以下的错误:

Refused to load the script 'https://code.jquery.com/jquery-2.2.2.min.js' because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:".

我们看到浏览器拒绝加载这个执行语句,因为它违反了**Content Security Policy(简称CSP)**机制,那什么是CSP机制呢?这里是Google官方给出的解释文档Content Security Policy (CSP)(自备梯子),想要了解更多可以参考另外几篇关于CSP的详细介绍:

通过阅读上述文档,我们了解到,为了一些安全方面的原因,比如大规模的跨站点脚本攻击等问题,Chrome扩展系统已遵循 Content Security Policy (CSP)的理念,引入了严格的策略使扩展更安全,同时提供创建和实施策略规则的能力,这些规则被用来控制扩展(或应用)能够加载的资源和执行的脚本。

通常情况下,CSP通过黑白名单的机制控制资源加载(或执行脚本)。为您的扩展制定一个合理的策略以使您认真地考虑哪些资源是扩展需要的,并让浏览器确保仅仅这些资源是您的扩展需要访问的。 这些策略为您的扩展提供host permissions之外的额外安全保障;这是一个额外的一层保护,而不是一种取代。

在Web上,此策略是通过HTTP头或meta元素定义的。在Chrome扩展系统中,不存在这两种方式。扩展是通过manifest.json文件定义的:

{
        ...,
        "content_security_policy": "[POLICY STRING GOES HERE]"
        ...
}

同时需要注意的是:没有定义 manifest_version 的扩展安装包默认是没有内容安全策略的。只有manifest_version为2的扩展才会默认开启内容安全策略。

由于CSP策略的影响,以下功能将被限制:

  1. 不执行Inline JavaScript

    Inline JavaScript和eval一样危险,将不会被执行,CSP规则将同时禁止内嵌

  2. 只加载本地脚本和资源

    只有扩展包内的脚本和资源才会被加载!通过Web即时下载的将不会被加载! 这确保您的扩展只执行已经打包在扩展之中的可信代码,从而避免了线上的网络攻击者通过恶意重定向您所请求的Web资源所带来的安全隐患。

找到了原因,现在就可以对代码进行一些修改了,我们将jquery-2.2.2.min.js的源代码下载下来,保存到本地,然后把popup.html中的修改为

<script src="scripts/jquery-2.2.2.min.js"></script>

再打开chrome console看一下,发现错误信息已经没有了,再检查下Sources信息,发现jquery-2.2.2.min.js已成功被加载,看来成功了。

那么,是否所有情况都可以采用这种办法呢?我们再来看一个例子,这次我们需要在Chrome Extension中嵌入一个百度地图的模块,按照百度地图的提供的Demo代码,我们只需要在页面中加入一下代码即可实现地图展示

<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=您的密钥"></script>
<script type="text/javascript">
// 百度地图API功能
var map = new BMap.Map("allmap");  // 创建Map实例
map.centerAndZoom("上海",15);      // 初始化地图,用城市名设置地图中心点
</script>

针对第二个script标签中对于百度地图调用部分的代码我们可以很容易的对它进行函数的封装,然后加入到本地js文件中,而不用写在popup.html页面内,而针对第一个script,我们能否也像上面的jquery一样将代码下载到本地呢?我们先来试一下,将http://api.map.baidu.com/api?v=2.0&ak=AIzaSyDY0kk**************oHxOIYM复制到浏览器中,按下回车,我们发现浏览器中出现以下信息:

(function(){ window.BMap_loadScriptTime = (new Date).getTime(); document.write('<script type="text/javascript" src="http://api.map.baidu.com/getscript?v=2.0&ak=AIzaSyDY0kk**************oHxOIYM&services=&t=20160401164342"></script>');})();

代码很好理解,先获取系统调用请求时的时间,在页面内嵌入一个script的标签,其中src的参数也发生了改变,加入了 servicest 两个参数,根据上文提到的 CSP 策略,我们显然不能将这串代码保存到本地js文档中再进行调用,于是我们再将 src 中的地址复制到浏览器中,按下回车,一下子出现了一大串代码,好像很成功,看样子这就是百度地图的js执行脚本文件,我们将该页面另存为baiduMap.js文档,然后在 popup.html 加入如下代码:

<script type="text/javascript" src="scripts/baiduMap.js"></script>

//同时将地图调用函数加入到script/popup.js中
<script type="text/javascript" src="scripts/popup.js"></script>

然后,我们再到浏览器中看下,发现报错了:

报错

提示拒绝 'evaluate a string as JavaScript' ,我们查阅官方文档就可以发现 eval() 函数同样是被CSP所禁止,类似的还有setTimeout(String), setInterval(String)new Function(String)

官方文档

那要怎么解决呢,官方文档提供了一种解决办法,叫做 放宽默认策略 (关于放宽默认策略更多的介绍,可以访问Content Security Policy Reference来获取),通过添加 'unsafe-eval' 来实现,即在mainfest.json中加入下面代码:

{
    ...
    "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
    ...
}

然后我们再来看一下Chrome Console中的调试信息

报错

发现之前的eval()函数已经被放宽了,但是又出现了一个新问题,应该是百度地图脚本文件在执行时需要调用http://api0.map.bdimg.com/http://api.map.baidu.com 中的相关内容,那么,我们能否也将上述两个HTTP源也进行策略放宽呢?我们发现官方文档中有这么一段描述:

Up until Chrome 45, there was no mechanism for relaxing the restriction against executing inline JavaScript. In particular, setting a script policy that includes 'unsafe-inline' will have no effect.

If you have a need for some external JavaScript or object resources, you can relax the policy to a limited extent by whitelisting secure origins from which scripts should be accepted. We want to ensure that executable resources loaded with an extension's elevated permissions are exactly the resources you expect, and haven't been replaced by an active network attacker. As man-in-the-middle attacks are both trivial and undetectable over HTTP, those origins will not be accepted.

翻译后大致意思就是:
直到 Chrome 45,都没有绕过“禁止Inline JavaScript执行”的办法。即使有意在脚本策略字段增加 'unsafe-inline' 也是没有任何作用的。
不过,如果您需要一些外部的JavaScript或者资源,您可以通过将HTTPS源的脚本加入白名单来放宽“只加载本地脚本和资源”策略。我们希望确保可执行资源的加载正是您所期望的,而不是被网络攻击者替换过的,而且通过HTTP进行的 man-in-the-middle 攻击很容易且不容易被察觉,因此白名单对于不安全的HTTP源是无效的。

官方文档说了,对http源的放宽策略是无效的,因此即使对 manifest.json 进行如下设置

{
    ...
    "content_security_policy": "script-src 'self' 'unsafe-eval' http://api0.map.bdimg.com/; object-src 'self'",
    ...
}

依然会在Chrome Console中出现同样的错误

报错

那好,既然HTTP源不可以,我们就去看看 http://api0.map.bdimg.com/ 有没有提供相应的HTTPS源,经测试,无法在浏览器中打开以下链接

https://api0.map.bdimg.com/getmodules?v=2.0&t=20140707&mod=canvablepath_lnpk2b,common_5w45zj,symbol_huc0ka,marker_fcztej

因此这种方法至此就行不通了。


既然将代码下载到本地的办法行不通,我们试一下直接在popup.html中调用百度地图api服务器上的脚本文件看看行不行,代码如下:

<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=AIzaSyDY0kk**************oHxOIYM"></script>

因为上文中提到,CSP策略不支持HTTP源,因此我们先要看看该脚本文件有没有对应的HTTPS源,在浏览器中输入

http://api.map.baidu.com/api?v=2.0&ak=AIzaSyDY0kk**************oHxOIYM

后发现,出现了同样的内容,

(function(){ window.BMap_loadScriptTime = (new Date).getTime(); document.write('<script type="text/javascript" src="http://api.map.baidu.com/getscript?v=2.0&ak=AIzaSyDY0kk**************oHxOIYM&services=&t=20160401164342"></script>');})();

于是我们再将src中的地址修改为HTTPS源的形式,在浏览器中打开

https://api.map.baidu.com/getscript?v=2.0&ak=AIzaSyDY0kk**************oHxOIYM&services=&t=20160401164342

惊喜的是浏览器中竟然出现了脚本文件的代码!!
于是我们修改popup.html中的代码为HTTPS源的形式

<script type="text/javascript" src="https://api.map.baidu.com/getscript?v=2.0&ak=AIzaSyDY0kk**************oHxOIYM&services=&t=20160401164342"></script>

同时,将 https://api.map.baidu.com/ 加入到白名单中

{
    ...
    "content_security_policy": "script-src 'self' 'unsafe-eval' https://api.map.baidu.com/; object-src 'self'",
    ...
}

满怀希望地打开Chrome Console,发现依旧出现错误 报错

发现不管是本地脚本加载还是服务器脚本链接加载,由于百度地图API脚本文件内部的数据调用请求都是HTTP形式,因此即使将脚本下载到本地,或者将 https://api.map.baidu.com/ 添加到白名单中,Chrome都是拒绝加载脚本内部发出的HTTP形式请求。


既然百度地图不行,那就来试试传说中的Google Map,调用很简单,在popup.html中加入以下代码

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDY*****************OIYM&sensor=false"></script>

//同时将地图调用函数加入到script/popup.js中
<script type="text/javascript" src="scripts/popup.js"></script>

然后将 https://maps.googleapis.com/ 加入到白名单中,

{
    ...
    "content_security_policy": "script-src 'self' 'unsafe-eval' https://maps.googleapis.com/; object-src 'self'",
    ...
}

发现地图出现啦!!并且可以正常使用

Google Map

再看一下Console中的信息

调试信息

干干净净,没有错误,为什么Google地图就可以了呢?我们看一下Network就知道了

Network

发现Google Map调用的所有数据请求通过 https://maps.googleapis.com/ 这个HTTPS源来实现的,因为上文中,https://maps.googleapis.com/ 已经加入了白名单,所以,Chrome也就不会拒绝加载了 ^_^

唯一存在的问题就是国内无法调用Google Map的相关服务,需要 番羽·土啬 才能使用,对于基于Google Map开发的拓展程序,国内就不适宜使用了……

最后总结: 大致讲解了下 CSP 的作用,以及一些需要注意的地方,写的比较粗略,尚存在较多不足,望批评指正

登录后回复,如无账号,请使用邀请码注册