防止xss 99%的都知道要做标签过滤,和标签属性过滤,50%知道非标签内容转义,40%知道httponly,10%的人知道 waf,只有1%的人知道它。
周末尝试用跑路技巧来强行引出 CSP ,故事有点牵强,本想大家都一起留言说如何解决这种 self-xss 的问题,但是效果不是很好,这里我自己回应下吧~~
对于这种”顽固型“存储内容,存在更高权限的用户编辑其他人低权限用户的内容的情况。下面这个方案应该能在一定程度上解决问题。
当然 csp 和 httponly 的指定也都是服务端输出的。waf 是一层可以帮助我们拦截掉一些攻击代码的网络防火墙,属于云产品范畴,大家可以自行搜索相关云产品。
今天主要和大家分享学习下 CSP (Content Security Policy)的知识,CSP 里定义的内容有很多,不仅可以防止被 XSS 还有被 iframe 或者 iframe 引用等
假如我们提供的是一个存储型的服务,比如博客。
案例 http://mengkang.net/demo/csp/...
<html>
<header>
<title></title>
</header>
<body>
<div id="blog">
<script>
alert(document.cookie);
</script>
</div>
</body>
</html>
也就是说"#blog"里面的内容是不可信的,一方面要做好展示时的过滤,假如我们没做好过滤,上面的 js 就会执行;另一方面,我们也不能随意修改用户的内容,所以即使展示时过滤了,而原始内容不能修改,当编辑用户的内容时,依然会触发xss
,也就是self-xss
,这主要攻击的就是有编辑其他人内容的人。
CSP 主要是为了解决跨站脚本攻击和数据注入攻击,它的核心原理是在服务端渲染页面的时候 http header 头里带上 CSP 协议,协议里面有一个nonce
(服务端随机生成的码,不需要存储),然后页面需要执行的 js 必须也必须带上该nonce
。
拦截攻击
案例 http://mengkang.net/demo/csp/...
<?php
$nonce = md5(uniqid());
$policy = "script-src 'nonce-" . $nonce . "';";
header("content-security-policy:". $policy);
?>
<html>
<header>
<title></title>
</header>
<body>
<div id="blog">
<script>
alert(document.cookie);
</script>
</div>
</body>
</html>
这样,用户写的博客里面的 js 就不会被执行了。并且控制台上会有报错记录
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'nonce-be6737d35f2c558943dae9ad44c369ee'". Either the 'unsafe-inline' keyword, a hash ('sha256-0FWdMKk2PWuytGHwpB/r+HwqVTWZoSWX/M+OKSueWHI='), or a nonce ('nonce-...') is required to enable inline execution.
但是如果页面有一些我们自己开发的 js 要执行怎么办?
内部引入 js 的放行
案例 http://mengkang.net/demo/csp/...
<?php
$nonce = md5(uniqid());
$policy = "script-src 'nonce-" . $nonce . "';";
header("content-security-policy:". $policy);
?>
<html>
<header>
<title></title>
</header>
<body>
<div id="blog">
<script>
alert(document.cookie);
</script>
</div>
<div id="notice">
<script nonce="<?php echo $nonce?>">
alert("我是自己人,别拦截");
</script>
</div>
</body>
</html>
原理就是我们自己的 js script 标签上加上了服务端渲染的nonce
值,所以能正常执行;而攻击者写的博客里面的js是写死的,即使他写了一个nonce
,但是他没法控制“被攻击者”打开页面时的nonce
值正好等于他写死的那个值。
外部引入 js 的放行
案例 http://mengkang.net/demo/csp/...
<?php
$nonce = md5(uniqid());
$policy = "script-src 'nonce-" . $nonce . "';";
header("content-security-policy:". $policy);
?>
<html>
<header>
<title></title>
</header>
<body>
<div id="blog">
<script>
alert(document.cookie);
</script>
</div>
<div id="notice">
<script nonce="<?php echo $nonce?>">
alert("我是自己人,别拦截");
</script>
</div>
<script src="test.js"></script>
</body>
</html>
test.js
是不能被引入的,需要改造成
<script src="test.js" nonce="<?php echo $nonce?>"></script>
如果外部引入 js 有动态插入的情况,一定要把nonce传递给外部 js。比如test.js
背后是一段服务端代码,里面会执行document.write
,解决方案是
<script src="test.js?nonce=<?php echo $nonce?>" nonce="<?php echo $nonce?>"></script>
test.js
在收到参数nonce 之后,对后面动态插入的js做类似上面的操作两类操作添加nonce
行内 js 改造
如果页面中有大量的行内 js,需要改造成内部引入 js 的方式
<span onclick="alert(1);"></span>
这样的js 是不能被执行的,需要改造成
<span id="myButton"></span>
<script nonce="<?php echo $nonce?>">
document.getElementById("myButton").addEventListener("click", myFunction);
function myFunction(){
alert(1);
}
</script>
最终版
加上各种浏览器的兼容性,还有CSP1和CSP2的兼容性之后得到以下经过实战项目的规则
content-security-policy: base-uri 'self';script-src 'self' 'unsafe-inline' 'unsafe-eval' 'report-sample' https: http: 'strict-dynamic' 'nonce-543c368ef6f944e1a93fa60d39687a02';frame-src 'self' a.baidu.com b.taobao.com ;worker-src blob: 'self' data:;object-src 'self' a.baidu.com;frame-ancestors *.aliyun.com;report-uri /csp/report;
可以参考这个去改下
frame-src
控制我们可以引入的页面域名frame-ancestors
控制谁可以引用我们report-uri
是 csp 拦截日志上报地址
原创码字不易,期待你的关注,我的公众号
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。