CSP
CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行。它的实现和执行全部由浏览器完成,开发者只需提供配置。
1.如何启用CSP
第一种方法启用CSP
通过返回HTTP响应头,例如在nginx中可以这样
add_header Content-Security-Policy "default-src 'self' www.harver.cn harver.cn 'unsafe-inline' blob: data: ; "; #嵌入白名单
第二种方法启用CSP
设置meta标签
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
2.限制选项
为了方便调试,下面将采用第二种方法于本地进行调试,并只给出关键代码
script-src
在JavaScript中脚本被分为2类:内联脚本、外部脚本。
内联脚本:将脚本写入到html标签中,例如
<a href="javascript:alert('我是内联脚本写法一')">查看结果</a>
<script> console.log("我是内部脚本脚本写法二");</script>
<button id="btn" onclick="doSomething()"></button>
外部脚本:以单独文件的形式存在,通过src引入
<script src="xxx.js"></script>
而script-src用于限制脚本加载的源,如果不指定 script-src
指令,则浏览器将允许来自任何来源的脚本。它可以包含下面这几种值:
-
'none'
:指空集;也就是说,没有URL匹配。单引号是必需的。 -
'self'
:仅允许来自当前域的脚本(仅包含外部脚本) -
'unsafe-inline'
:允许当前域下的内联脚本以及事件监听函数 -
'unsafe-eval'
:允许使用eval()来执行脚本 -
nonce
:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行 -
hash
:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。 -
自定义域名:自定义域名不需要加引号,直接写域名即可,多个域名用空格隔开
-
还有一些,例如
blob:
、data:
、https:
等 -
*
:表示任意源,但*
只匹配网络方案('http', 'https', 'ws', 'wss'
)的url,或者方案与'self'
方案匹配的url。
多个值也可以并列,用空格分隔。
示例1:对于'self'
以及'unsafe-inline'
使用
设定CSP策略,并给出内联脚本、外部脚本,我们最开始只设定对于外部脚本的限制
<!-- ./index.html -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
<body>
<a href="javascript:alert('我是行内脚本')">查看结果</a>
<script src="./test.js"></script>
<!-- <script>
console.log("我是内部脚本");
</script> -->
</body>
创建外部脚本
// ./test.js
console.log('我是外部脚本');
启动服务,打开页面,这时只执行了外部脚本,我们允许本域下的外部脚本执行,浏览器给我们通过了
点击a标签,会发现浏览器给我们了一个错误,这是由于我们只让浏览器允许外部脚本执行,对于内联脚本是不允许的
我们修改一下CSP策略,并且再增加几行代码
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
<body>
<a href="javascript:console.log('我是a标签下的脚本')">查看结果</a>
<button onclick="console.log('我是button标签下的脚本')">我是一个button,点击我触发click事件</button>
<script src="./test.js"></script>
<script>
console.log("我是内联脚本");
</script>
</body>
点击a标签,脚本正常执行,点击button事件正常触发,同时script下的内联脚本正常执行
示例2:对于nonce
使用,
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-test_nonce'">
<body>
<a href="javascript:console.log('我是a标签下的脚本')">查看结果</a>
<button onclick="console.log('我是button标签下的脚本')">我是一个button,点击我触发click事件</button>
<script nonce="test_nonce" src="./test.js"></script>
<script nonce="test_nonce">
console.log("我是test_nonce脚本");
</script>
</body>
这时候就不会区分外部,内联了,只允许设置了nonce并且匹配成功的script执行
注意,设置了nonce之后会忽视掉
'unsafe-inline'
,也就是设置了nonce之后,非script的内联脚本执行不了,例如button和a标签,这一方面可以有效防止XXS攻击,另一方面可能会让开发人员掉入一些坑里,例如JSP上的监听事件可能会需要这种写法,后续若找到了解决办法,会及时更新~同时开发中一般不推荐使用
'unsafe-inline'
,因为XXS攻击会通过内联脚本注入
示例3:对于hash
的使用
<meta http-equiv="Content-Security-Policy" content="script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='">
<script>alert('Hello, world.');</script>
计算
hash
值的时候,<script>
标签不算在内这个就不实战了,直接套的阮大佬的案例,个人认为脚本文件变化性太大,利用
hash
可能会需要更多的开发量,不如nonce
,当然也只是个人理解
示例4:对于自定义域名的使用,本地启动于5173端口
对于自定义域名,
style-src、script-src、font-src
等等其实自定义域名的格式是通用的,这里找到了一个css的资源,需要用到style-src和font-src
,如果是js资源,只使用script-src
即可
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; style-src 'self'; font-src 'self';">
<body>
<a href="javascript:console.log('我是a标签下的脚本')">查看结果</a>
<button onclick="console.log('我是button标签下的脚本')">我是一个button,点击我触发click事件</button>
<script src="./test.js"></script>
<script>
console.log("我是test_nonce脚本");
</script>
</body>
正常执行
现在我们引入一个外部的css资源(非同源)
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css"
integrity="sha512-fHwaWebuwA7NSF5Qg/af4UeDx9XqUpYpOGgubo3yWu+b2IQR4UeQwbb42Ti7gVAjNtVoI/I9TEoYeu9omwcC6g=="
crossorigin="anonymous" />
不出意外,浏览器报错
这时候就需要将该资源所处的域名加入到我们的策略中
<meta http-equiv="Content-Security-Policy"
content="script-src 'self' 'unsafe-inline'; style-src 'self' cdnjs.cloudflare.com; font-src 'self' cdnjs.cloudflare.com;">
当然也可以这样
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' cdnjs.cloudflare.com;">
这样就没问题了
示例5:对于blob的使用
var maliciousCode = "alert('This is a malicious script!');";
var maliciousBlob = new Blob([maliciousCode], { type: 'application/javascript' });
var maliciousUrl = URL.createObjectURL(maliciousBlob);
var scriptElement = document.createElement('script');
scriptElement.src = maliciousUrl;
document.head.appendChild(scriptElement);
blob:主要是针对Blob形式创建的脚本,当使用这种方式创建脚本的时候,需要将blog也加入到策略中,例如动态创建worker,策略写法为script-src blob:
style-src
用于控制样式表的策略,与script-src
用法基本相同
img-src
用于控制图像的策略,例如<img />
,与script-src
用法基本相同
media-src
用于控制媒体文件(音频和视频)的策略,与script-src
用法基本相同
font-src
用于控制字体文件的策略,与script-src
用法基本相同
还有一部分也与script-src用法基本相同
object-src
:插件(比如 Flash)child-src
:框架frame-ancestors
:嵌入的外部资源(比如<frame>、<iframe>、<embed>和<applet>
)。用于控制iframe
嵌入,若不希望自己站点被其他网站嵌入,那么设置为'none'
(例如github)connect-src
:HTTP 连接(通过XHR、WebSockets、EventSource
等)worker-src
:worker
脚本manifest-src
:manifest文件,link设置为rel="manifest"
;这个在PWA相关的网站可能会被使用(例如一个站点提示:将此站点安装,一般就说明该站点配置了manifest
)base-uri
:限制<base#href>
form-action
:限制<form#action>
default-src
用于设置上面限制的各个选项的默认值
例如
default-src 'self'
表示将script-src
、style-src
、img-src
等等都设置为'self'
,如果同时还设置了单个选项的策略,那么会以单个选项的值优先,例如
<meta http-equiv="Content-Security-Policy"
content="default-src 'self' 'unsafe-inline' ; script-src 'self' ;style-src 'unsafe-inline'; img-src * ;">
那么,script-src
的实际策略是'self'
,style-src
和img-src
同理
3.其他限制
-
block-all-mixed-content
:已弃用。HTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启) -
upgrade-insecure-requests
:自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议html<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
更细粒度上限制对 http 资源的访问,可以将个别指令的值设置为 "https:"。例如,为了限制对不安全的走 http 协议的图片的访问,可以这么做:
html<meta http-equiv="Content-Security-Policy" content="img-src https:">
-
plugin-types
:已弃用 -
sandbox
:浏览器行为的限制,比如不能有弹出窗口等。CSP: sandbox - HTTP | MDN (mozilla.org)
4.违规上报
使用report-uri
来进行违规上报
report-uri
只能通过http响应头的方式来进行设置,无法再meta
中进行设置,在防止时,浏览器会把注入行为的报告通过POST
方法,发送一个JSON对象给我们给定的URL
具体使用为 report-uri
+ URL
例如,我希望在策略拒绝时,发送给我们的/csp/report
接口,首先你得存在着这样一个post接口,然后设置report-uri
add_header Content-Security-Policy "default-src 'self' www.harver.cn harver.cn 'unsafe-inline' blob: data: ; report-uri /csp/report;";
这样在防止时,浏览器就会调用你的网站baseURL
+ /csp/report
这样一post接口将注入行为的报告提交给你的服务器,当然我没有提供这样一个接口(以后也许会)
5.只上报不防止
Content-Security-Policy-Report-Only
表示不执行限制选项,只是记录违反限制的行为。必须与report-uri
选项配合使用
add_header Content-Security-Policy-Report-Only "default-src 'self' www.harver.cn harver.cn 'unsafe-inline' blob: data: ; report-uri /csp/report;";
这样设置之后,该执行的正常执行,不会做任何阻拦,但是违反策略时,会调用我们给的URL,将违反行为的上传给服务器