分享 CSP (Content Security Policy) 解决方法

经过近一个半Sprint的摸索和尝试,总算给项目(Portal)穿上了CSP这层安全盔甲,在这里忍不住要给各位吐槽下, 分享处理CSP问题过程中的经验。
什么是CSP ? CSP is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement or distribution of malware. -MDN 简而言之,CSP规范可以设定资源(javascript, css, image, frame ….)加载域的白名单, 也可以限制内联Javascript的执行,避免由于系统漏洞导致中间人利用脚本注入攻击系统。

Portal实现哪些CSP Requirements ?
目前根据CSP规范 Portal 完成了 1. 资源加载来源的检测 2. 重构代码消除所有内联Javascript

Portal资源加载规则是什么? 目前 Portal没有跨域资源加载的情况,所以设置为self 本域有效,具体可参考CSP level 2规范CSP evel 2规范 Content-Security-Policy: default-src ‘self’; img-src ‘self’ data:; style-src ‘self’ ‘unsafe-inline’; script-src ‘self’ ‘unsafe-eval’ ‘nonce-{random string}’; object-src ‘none’; child-src ‘none’

CSP如何检测内联Javascript是否合法? 根据CSP level2定义,在CSP规则里引入nonce,浏览器根据CSP规则进行检测,只有申明nonce并且值与Response Header里值相同才被视为合法。 除此之外HTML里的JS事件都被认为非法。

W如何在Portal消除内联JS? 首先根据CSP 规则增加nonce定义,修改所有含有内联JS JSP页面, 其次消除所有HTML JS事件 , 最后修改所有空链接实现。 如下写法是不合法的

1.无nonce申明

1
2
3
<script type="text/javascript">

</script>

2.HTML JS

1
<input onclick="javascript: alert('test')">

3.空链接实现

1
2
<a  href="javascript:;"> 
<a href="javascript:void(0)">

Portal 实现CSP过程遇到的问题有哪些?

1. 清理内联JS
根据规范要求,所有HTML事件通过JS 来绑定 ,而不要使用HTML事件属性,空链接要改成

1
<a href="#">

形式,为了避免页面刷新还需要阻止默认事件行为的触发。

2.处理bypass CSP检测
Portal大部分页面加载通过ajax 方式jquery 的load方法实现,由于采用’unsafe-eval’规则,允许通过load执行HTML中javascript, 绕过目前CSP的检测,需要改写loadPage方法, 详细细节参考

测试Cases 1 JSP引入非本域javascript或 css, 浏览器阻止引用
<%= _(‘root’) %>

Case2 假如Web.jsp被注入恶意脚本,浏览器阻止脚本执行

Case3 假如ajax加载页面被注入恶意脚本,程序能检测并阻止脚本执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
  /**
* CSP (Content Security Policy) requirement
* Comment on illegal inline js code to block inline js
* without or wrong nonce attribute execution
*
* refer to: https://blogs.dropbox.com/tech/2015/09/csp-the-unexpected-eval/
*
* @param {string} htmltxt [get it via ajax call(load)]
* @return {DOM} [comment illegal inline js code]
* @author Feng Wu
*/

function handleCSPNonce(htmltxt) {
var ut = this,
html = $(htmltxt),
csp_script_nonce = html.find("#csp_nonce").val(),
isscript = /^(\<script)(\S|\s)+/ig.test(htmltxt),
inlinejs, comment,
errmsgs = ["Refused to execute script because CSP_SCRIPT_NONCE" +
" is defined and the nonce doesn't match."];


if(!isscript) { //htmltxt includes javascript
inlinejs = html.find("script").filter(function() {
return !!$(this).attr("src");
});
if(inlinejs.length !== 0) {
inlinejs.each(function() {
var el = $(this),
js_nonce = el.attr("nonce") || "",
script, parent, errmsg;

if(csp_script_nonce !== js_nonce) {
script = el.text();
parent = el.parent();
el.remove();
errmsgs.push(["expected:<", csp_script_nonce, "> but was:<", js_nonce, ">"].join(""))
errmsg = errmsgs.join("");
comment = ["<!--", errmsg, "<script type='text/javascript'>", script, "</script> -->"].join("");
ut.log(errmsg, "error");
$(parent).append(comment);
}

});
}
} else { //htmltxt is inline javascript
inlinejs = html;
errmsg = errmsgs.join("");
comment = ["<!--", errmsg, "<script type='text/javascript'>", htmltxt, "</script> -->"].join("");
ut.log(errmsg, "error");
return $(comment);
}

return html;
}

随着安全标准和规范地不断完善,浏览器安全性也在不断改进,需要我们能够了解并且使用到项目中,实现的过程中往往也需要改变我们的编程习惯,摸索的过程是痛苦的但痛苦过后就是方向和未来。