防范JavaScript中的DOM XSS安全漏洞
DOM XSS攻击是指黑客的恶意代码因修改浏览器中的DOM环境而被执行。
DOM XSS注入可以通过各种方式发生。例如:
- 接受要加载的代码的URL的元素属性的设置器,如HTMLScriptElement.src
- 接受要执行的代码的元素属性设置器,如HTMLScriptElement.text
- 直接执行代码的函数,如eval
因此,需要采取必要的措施避免这些问题。
Trusted Types API
与基于服务器端XSS漏洞相比,基于DOM的XSS漏洞不断增加,所以Google Chrome的团队推出了一个名为Trusted Types API的API,来控制这些DOM XSS的安全漏洞。
这是因为DOM XSS很容易引入,但更难检测。
Trusted Types API如何防止DOM XSS攻击?
Trusted Types API能够从根源上解决XSS问题。
DOM API在默认情况下是不安全的。例如:
eval('foo()');
document.createElement('div').innerHTML = '<foo>';
document.createElement('a').setAttribute('onclick'', 'foo()');
很容易向这些注入恶意脚本或恶意HTML。
为了避免这种情况,该API能够将内容安全策略(CSP)的HTTP响应头设置为Content-Security-Policy: trusted-types \*
,以只利用可信的类型。这将使开发人员能够阻止危险的注入,使他们在默认情况下是安全的。
可以按以下方式启用:
Content-Security-Policy: trusted-types;
Content-Security-Policy: trusted-types 'none';
Content-Security-Policy: trusted-types <policyName>;
Content-Security-Policy: trusted-types <policyName> <policyName> 'allow-duplicates';
Trusted-types指令指示浏览器建立不可欺骗的、类型化的值,以代替字符串传递给DOM XSS。
这里的主要想法是将对象传递给DOM,而不是字符串。DOM支持对象的传递。
elem.innerHTML = { toString: function() { return 'Hello World' }};
elem.innerHTML // returns Hello World
Trusted Types API推荐的是传递类型化的对象而不是普通的JS对象。
这将使DOM汇拒绝字符串,只接受匹配的类型。
在实践中使用可信的类型API
如果打算在你的项目中使用Trusted Types API,首先,需要找出不符合信任类型的地方。
检查是否有违反信任类型的情况
将以下HTTP响应头添加到需要检查违规的文件中。
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //mysite.com/cspViolations
然后,所有的违规行为将被报告给//mysite.com/cspViolations
。这不会妨碍网站的任何功能。
受信任的类型违规报告
当检测到信任类型违规时,它将被发送到一个使用report-uri配置的报告。例如,如果你向innerHTML传递了一个字符串,将生成类似于下面的报告。
{
"csp-report": {
"document-uri": "https://mysite.com",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 20,
"column-number": 12,
"source-file": "https://mysite.com/dashboard.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
这将帮助确定哪些文件中的哪行代码会导致你的网站出现DOM XSS漏洞。
解决违规问题
可以用一些方法来消除可信类型的违规。
1.重写违规的代码
例如,如果你有一段代码,如el.innerHTML = '';,这可以重新写成如下。
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
这就避免了直接给innerHTML分配一个字符串。
2.使用一个库
像DOMPurify这样的库可以用来净化HTML,它返回的HTML被包裹在一个TrustedHTML对象中。这就不允许浏览器产生违规。
3.创建一个可信的类型策略
你可以自己创建一个受信任的类型对象,而不是使用一个库或删除有漏洞的代码。可信的类型策略对其输入强制执行安全规则。
- 第1步 - 创建一个策略
if (window.trustedTypes && trustedTypes.createPolicy) {
const escapeHTMLPolicy = trustedTypes.createPolicy('escapePolicy',
{
createHTML: string => string.replace(/\</g, '<')
});
}
这个规则将省略<字符,以防止创建新的HTML元素。createPolicy()返回一个策略对象,将返回值包裹在一个正确的类型中;在这个例子中是TrustedHTML 。
- 第2步 - 使用策略
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
如果你不能改变代码(例如,从CDN采购第三方库),你可以使用一个默认的策略。
if (window.trustedTypes && trustedTypes.createPolicy) {
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}
这是一个政策和净化库的结合。
一旦你解决了所有的违规问题,你就可以在你的代码中强制执行信任类型。这是按以下方式进行的。
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //mysite.com/cspViolations
你可能注意到,与以前的实现相比,这里没有包括-Report-Only后缀。
我们只看了解决HTML违规的问题。然而,Trusted Types API有能力在以下方面检测违规行为并执行规则:
- HTML
- 脚本
- 脚本URL
- 脚本URL
强制执行受信任类型的好处
- 减少你网站的攻击面 - 应用程序是安全的
- 在编译时和运行时进行安全验证
- 向后兼容--能够使用可信的类型来代替字符串
- 补充其他安全解决方案,如用于服务器端XSS的CSP
浏览器的兼容性
不幸的是,Firefox和Internet Explorer并不支持Trusted Types。所以如果你是Chrome浏览器用户就很幸运了。