如何避免基于DOM的XSS攻击
2021-03-01
背景
DOM XSS一直以来是网站上最普遍和最危险的安全漏洞之一,常见的两种情况,要么是服务端输出了不安全的HTML代码,要么是JavaScript调用了控制用户内容的危险函数。
为了避免服务端的XSS,不要通过连接字符串来生成HTML,而是使用安全的上下文转义模板。可以使用库common-tags里的html
方法,得到安全的HTML片段。
对于第二种,浏览器提供了Trusted Types,避免在客户端的XSS。
Trusted Types 介绍
为了开启可信类型检查,我们需要在HTML头部加上
<meta http-equiv="Content-Security-Policy" content="trusted-types unsafe escape; require-trusted-types-for 'script'">
可信类型通过锁定下面这些具有风险的函数或属性来规避问题,甚至在一些框架里也会让你避免使用这些特性,比如在React内也有提及。
- 脚本操作:
设置<script>
的src
属性或者元素内的文本内容 - 用字符串生成HTML:
innerHTML
,outerHTML
,insertAdjacentHTML
,<iframe> srcdoc
,document.write
,document.writeln
和DOMParser.parseFromString
- 执行插件内容:
<embed src>
,<object data>
和<object cadebase>
- JavaScript运行时:
eval
,setTimeout
,setInterval
和new Function()
在赋值给上述属性或传参给上述方法时,如果只使用字符串的话会报错,因为浏览器并不知道他们是可信的。点击这个例子
我在给HTML加上meta
后,又写了如下的代码
document.body.innerHTML = 'throws error'
打开控制台可以看到如下错误:
可信类型会阻止使用带有字符串的DOM XSS接收器,这大大减少了应用程序的DOM XSS攻击面。
使用 Trusted Types
通过创建可信类型策略,我们可以正常使用以上方法。
首先引入通过script
标签引入库
<script src="https://w3c.github.io/webappsec-trusted-types/dist/es5/trustedtypes.build.js" data-csp="trusted-types unsafe escape; require-trusted-types-for 'script'"></script>
然后再创建一个策略:
var escapePolicy = trustedTypes.createPolicy('escape', {
createHTML: function(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
},
});
var escapedValue = escapePolicy.createHTML('come on');
document.body.innerHTML = escapedValue
这样就可以针对用户自定义的特殊情况使用以上方法了。
总结
web现在发展得很快,新建一个网站会引入大量的第三方库,最终生成的HTML并不可控,我们自己如果去做大量的策略兼容显然不大现实,我们可以使用像DOMPurify 这样的第三方库来帮助实现HTML的XSS清理。
同时,在编写代码时也该注意避免使用innerHTML,如:
element.innerHTML = '<img src=abc.jpg>';
可以替换成
element.textContent = '';
const img = document.createElement('img');
img.src = 'abc.jpg';
element.appendChild(img);