本文为翻译版本,原文请查看 https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet
介绍
谈到XSS攻击,有三种公认的形式,Stored、 Reflected 和 DOM Based XSS。
XSS Prevention Cheatsheet可以有效地解决 Stored、 Reflected XSS攻击, 本检查单解决了 DOM Based XSS攻击,是 XSS Prevention Cheatsheet 的延伸。
为了理解DOM Based XSS攻击, 你需要理解Reflected和Stored XSS攻击与DOM Based XS的根本区别。
Reflected and Stored XSS是服务器端代码执行产生的问题,DOM based XSS是浏览器端代码执行产生的问题。
所有的代码都来源于服务器, 这意味着应用拥有者有责任保证应用免受XSS攻击,不管攻击是哪种类型XSS攻击。
当浏览器渲染HTML和其他关联内容,例如 CSS JS etc, 对于每种不同的上下文使用不同的规则进行渲染。
HTML解析器负责数据如何被显示并在页面上如何布局,并进一步分解为标准的环境,例如 HTML HTML属性 URL 和 CSS。JavaScript or VBScript解析器负责解析和执行脚本代码,每种解析器有不同的语法,这使得创建统一的预防漏洞的规则变得困难。复杂点在于混合了不同的意义和每种子环境(HTML, HTML attribute, URL, and CSS)对转码的处理方法。
本文将HTML, HTML attribute, URL, and CSS环境定义为子环境,因为每种环境都可以被JS环境访问和执行。
在JS代码中,主环境是JS,但是通过右标签(right tags)和环境关闭字符, 攻击者可以攻击其他四种环境,使用相同意义的JS DOM方法。
下面就是一个漏洞例子,发生在JS环境,由于HTML子环境被攻击:
<script>
var x = ‘<%= taintedVar %>’;
var d = document.createElement(‘div’);
d.innerHTML = x;
document.body.appendChild(d);
</script>
下面让我们按个查看下各个子环境。
规则#1 在脚本中将不可信数据插入到HTML前,先执行HTML转义、然后执行JS转义
存在几种方法和属性可以在JS中直接渲染HTML环境,如果这些方法和属性遇到了不可信的数据,那么XSS漏洞就发生了。
例如
-- 属性
element.innerHTML = “<HTML> Tags and markup”;
element.outerHTML = “<HTML> Tags and markup”;
--方法
document.write(“<HTML> Tags and markup”);
document.writeln(“<HTML> Tags and markup”);
指导方针
为了保证动态更新DOM中的HTML是安全的, 我们推荐对不可信数据进行
a) HTML编码
b)JS编码
如下面这些例子所示
element.innerHTML = “<%=Encoder.encodeForJS(Encoder.encodeForHTML(untrustedData))%>”;
element.outerHTML = “<%=Encoder.encodeForJS(Encoder.encodeForHTML(untrustedData))%>”;document.write(“<%=Encoder.encodeForJS(Encoder.encodeForHTML(untrustedData))%>”);
document.writeln(“<%=Encoder.encodeForJS(Encoder.encodeForHTML(untrustedData))%>”);
注意: Encoder.encodeForHTML() and Encoder.encodeForJS() 仅仅是名字上的编码器,其中内部的实现在本文后面描述。
规则#2 在脚本中将不可信数据插入到HTML属性前,先执行JS转义
对于不执行代码的属性,即非event handler, CSS, and URL属性,只需要在JS中对属性值做JS转义。
var x = document.createElement(“input”);
x.setAttribute(“name”, “company_name”);
x.setAttribute(“value”, ‘<%=Encoder.encodeForJS(companyName)%>’);
var form1 = document.forms[0];
form1.appendChild(x);
同时,下面的例子指出,如果添加HTML转码是不对的,如果值被设置为“Johnson & Johnson”, 并且经过HTML转码,则结果为 “Johnson & Johnson”。说明,通过DOM属性接口修改DOM,是不应用HTML解析规则的。
var x = document.createElement(“input”);
x.setAttribute(“name”, “company_name”);
// In the following line of code, companyName represents untrusted user input
// The Encoder.encodeForHTMLAttr() is unnecessary and causes double-encoding
x.setAttribute(“value”, ‘<%=Encoder.encodeForJS(Encoder.encodeForHTMLAttr(companyName))%>’);
var form1 = document.forms[0];
form1.appendChild(x);
规则#3 将不可信数据插入到事件处理句柄 和 JS 代码中,一定要小心
放动态数据到JS代码中是特别危险的,因为JS编码有不同的语义,相比较其它环境的编码。
在很多例子中,JS转码并不能阻止代码的执行。因此,首要的推荐是不要将不可信数据插入JS代码中。下面的代码演示了即使转义仍有风险的情况:
var x = document.createElement("a");
x.href="#”;
// In the line of code below, the encoded data on the right (the second argument to setAttribute)
// is an example of untrusted data that was properly JavaScript encoded but still executes.
x.setAttribute("onclick", "\u0061\u006c\u0065\u0072\u0074\u0028\u0032\u0032\u0029");
var y = document.createTextNode("Click To Test");
x.appendChild(y);
document.body.appendChild(x);
这是因为这些方法都将JS字符串,当做JS代码,并进行执行。具有相同风险的其他方法 (setTimeout, setInterval, new Function, etc.)。
规则#4 将不可信数据插入到CSS属性前先执行JS转义
暂无研究,待添加。
规则#5 将不可信数据插入到URL属性前,先执行URL转义,后执行JS转义
暂无研究,待添加。
利用JS开发安全应用指导方针
DOM based XSS及其难于根治, 由于其攻击面非常广,且跨浏览器的标准化。下面尝试给出指南,以帮助开发者开发JS应用的时候, 减少XSS漏洞。
1、 不可信的数据应该做为只显示的文本,不应该作为 JS代码或者标签被执行。
2、 对不可信数据总是采用JS转义,并添加引号的方法,让其出现在JS代码中。
var x = “<%=encodedJavaScriptData%>”;
3、 使用 document.createElement(“…”), element.setAttribute(“…”,”value”), element.appendChild(…)
, etc. 去创建动态接口。 其中, setAttribute
也不是特别安全, 对于一些将值作为JS代码的属性, 还是有安全风险, 例如onclick or onblur, 其他安全属性包括 align, alink, alt, bgcolor, border, cellpadding, cellspacing, class, color, cols, colspan, coords, dir, face, height, hspace, ismap, lang, marginheight, marginwidth, multiple, nohref, noresize, noshade, nowrap, ref, rel, rev, rows, rowspan, scrolling, shape, span, summary, tabindex, title, usemap, valign, value, vlink, vspace, width
4、 避免使用HTML渲染方法
element.innerHTML = “…”;
element.outerHTML = “…”;
document.write(…);
document.writeln(…);
5、 理解贯穿JS代码中不可信数据的数据流,如果你必须使用4中的接口,请务必记住先执行HTML转义,后执行JS转义。
6、 关于eval()和不可信数据。
7、 限定不可信数据只作为右边值操作。作为左边值可以被执行,例如location
, eval()
var x = “<%=properly encoded data for flow%>”;
8、DOM中的URL转义需要注意字符集问题。
9、Limit access to properties objects when using object[x] accessors.
10、 在ECMAScript 5沙箱中运行JS代码,是的JS接口不被危害。
11、Don’t eval()
JSON to convert it to native JavaScript objects. Instead use JSON.toJSON()
and JSON.parse()
减轻DOM Based XSS攻击的常见问题
复杂上下文
一个点有两层含义, 首先是JS变量, 其次对于目标应用是URL
<a href=”javascript:myFunction(‘<%=Encoder.encodeForJS( ↩ Encoder.encodeForURL(untrustedData))%>’, 'test');”>Click Me</a>
...
<script>
Function myFunction (url,name) {
window.location = url;
}
</script>
如果客户端使用了JS版本的URL转义库,可以做如下写法
<!--server side URL encoding has been removed. Now only JavaScript encoding on server side. -->
<a href=”javascript:myFunction(‘<%=Encoder.encodeForJS(untrustedData)%>’, 'test');”>Click Me</a>
...
<script>
Function myFunction (url,name) {
var encodedURL = ESAPI4JS.encodeForURL(url); //URL encoding using client-side scripts
window.location = encodedURL;
}
</script>
转义库不一致性
有多种开源库,每种实现有差异
- ESAPI
- Apache Commons String Utils
- Jtidy
- Your company’s custom implementation.
转义误解
不是说转义后就绝对安全,例如
1 下面代码在 content-type 为 xhtml文档中可以执行
<script>
alert(1);
</script>
2 下面代码,转义丢失, 接口执行可以绘制任何DOM
<form name=”myForm” …>
<input type=”text” name=”lName” value=”<%=Encoder.encodeForHTML(last_name)%>”>
…
</form>
<script>
var x = document.myForm.lName.value; //when the value is retrieved the encoding is reversed
document.writeln(x); //any code passed into lName is now executable.
</script>
通常安全方法
通常认为 innerText 不会执行代码, 可以代替innerHTML减轻XSS攻击,但是也依赖标签, 下面例子可以执行代码
<script>
var tag = document.createElement(“script”);
tag.innerText = “<%=untrustedData%>”; //executes code
</script>