说说如何防御 DOM Based 类型的 XSS 攻击

2,638 阅读2分钟

普通的 XSS,是通过服务端(比如模板技术)将数据输出到 HTML 中。而 DOM Based 类型 的 XSS,是通过 JS 将数据输出到 HTML 中。

1 失败防御案例一

请看下例:

<script type="text/javascript">
    var x='<来源于 input 输入框>';
    document.write("<a href='"+x+"'>demo</a>")
</script>

这里的 x 变量,其值来源于用户所输入的内容。网站开发者使用普通 XSS 的防御方法(DefaultEncoder.getInstance().encodeForJavaScript(str))对该变量进行编码,以避免 XSS 攻击。

假设,攻击者使用 'onclick=alert('dom_based_xss');//' 作为输入内容,那么经过 encodeForJavaScript 编码,变量 x 变为这样:

<script type="text/javascript">
    var x='\x27onclick\x3Dalert\x28\x27dom_based_xss\x27\x29\x3B\x2F\x2F\x27';
    document.write("<a href='"+x+"'>demo</a>")
</script>

攻击结果:

难道 ESAPI 也防御不了基于 DOM Based 类型的 XSS 攻击了吗?我们来分析分析。

这是因为 encodeForJavaScript 只能保护 JS。而编码过的恶意脚本,通过 document.write() 方法输出到 HTML 页面时,又会被解码还原回去,从而导致 XSS 攻击成功。

2 失败防御案例二

那是否对上述场景应用 HtmlEncode 就解决问题了呢?请看下面这一段代码:

var y=xxx';
document.write("<a href=# onclick='alert(\""+y+"\")'>demo2</a>");

假设攻击者编写的攻击字符串是这样的:正常逻辑");alert('dom_based_xss');///,经过 HtmlEncode 编码为:&#x6b63;&#x5e38;&#x903b;&#x8f91;&quot;&#x29;&#x3b;alert&#x28;&#x27;dom_based_xss&#x27;&#x29;&#x3b;&#x2f;&#x2f;&#x2f;

攻击结果:

第一次仍然会执行正常逻辑,第二次就被 XSS 注入攻击咯。所以单纯使用 HtmlEncode 编码,仍然防不住被攻击。

3 终极防御方法

终极防御方法为:输出到 <script> 时,先使用 encodeForJavaScript 对其编码。然后在 doucment.write() 方法中做判断,如果是输出到 HTML 页面,则再使用 encodeForHTML 二次编码;如果是输出到 JavaScript,则使用 encodeForJavaScript 二次编码。这样,才能确保万无一失。

可能发生 DOM Based 类型的 XSS 攻击,有以下这些方法:

  • document.write()
  • document.writeln()
  • xxx.innerHTML
  • xxx.outerHTML
  • innerHTML.replace()
  • document.attachEvent()
  • window.attachEvent()
  • document.location.replace()
  • document.location.assign()

以下这些地方有可能成为 DOM Based 类型的 XSS 攻击字符串的注入点,也需要注意防范:

  • window.location(href)
  • window.location(hash)
  • window.name
  • document.referrer
  • document.cookie
  • localstorage
  • XMLHttpRequest 响应数据