本文主要介绍如何对XSS攻击进行防御

HttpOnly

  1. 定义:浏览器禁止页面的JS访问带有HttpOnly属性的Cookie。

    HttpOnly并非防止出现XSS,而是防御XSS后的Cookie劫持攻击。

  2. 作用方式

    在服务器对浏览器的请求进行回复,Set-Cookie时对cookie进行标记,服务器可能会设置多个Cookie,而HttpOnly可以有选择性地加在任何一个Cookie值上,因此可以仅将其加在用于认证的关键cookie上面。

输入检查

  1. 定义:

    目前Web开发的普遍做法,是同时在客户端JavaScript中和服务器端代码中实现相同的输入检查。

    原因:

    • 客户端检查提高用户体验:在客户端进行输入验证可以即时反馈错误信息给用户,无需等待服务器响应。节约了服务器资源的同时,也可以快速指导用户纠正错误,提高整体的用户体验。
    • 服务端检查保障安全性:尽管客户端验证可以提供即时反馈,但它可以被绕过。用户可以修改客户端代码,或者直接通过HTTP请求工具发送请求绕开客户端。因此,服务端验证是必不可少的,它保证了即使客户端验证被绕过,服务端仍然能够保护应用免受恶意数据的影响。
  2. 作用方式:存在比较多的开源xssfilter

  3. 局限性

    1)语境

    XSS Filter在用户提交数据时获取变量,并进行XSS检查;但此时用户数据并没有结合渲染页面的HTML代码,因此XSS Filter对语境的理解并不完整。

    例如:

    1
    <script src="$var" ></script>

    其中$var是用户可以控制的变量。用户只需要提交一个恶意脚本所在的URL地址,即可实施XSS攻击。

    如果是一个全局性的XSS Filter,则无法看到用户数据的输出语境,而只能看到用户提交了一个URL,很可能会漏报。因为在大多数情况下,URL是一种合法的用户数据。

    2)字符处理

    如果仅仅是对 <" 等字符进行粗暴的转义或替换处理,可能会改变用户数据的语义。

输出检查

安全的编码函数

  1. 可以使用编码函数对输出中的敏感符号进行转义

    例如:JavascriptEncode对除数字、字母以外的所有字符,使用十六进制的方式进行编码。

    还有很多的编码函数,如XMLEncode、JSONEncode等。

  2. 在转义的时候要关注上下文环境

    不同的上下文环境(HTML文档结构和JavaScript程序逻辑)对数据的解析方式不同。如果在不适当的上下文中使用错误的转义方法,可能不会产生预期的防护效果,仍然允许XSS攻击的发生。所以要在正确的地方使用正确的编码方式

    例如:

    a. 初始HTML代码

    1
    2
    3
    htmlCopy code<body>
    <a href=# onclick="alert('$var');">test</a>
    </body>

    这里的意图是在用户点击链接时弹出变量$var的内容。

    b. 用户输入和攻击向量

    1
    2
    3
    javascript
    Copy code
    $var = htmlencode("');alert('2");

    用户通过输入特制的字符串试图结束原有的alert函数调用,并开始一个新的alert函数,实际上注入了额外的JavaScript代码。

    c. HTML编码应用后的结果

    1
    2
    3
    htmlCopy code<body>
    <a href=# onclick="alert('&#x27;&#x29;&#x3b;alert&#x28;&#x27;2');">test</a>
    </body>

    这里,虽然特殊字符被HTML编码,看似安全,但问题在于浏览器的解析顺序。

    浏览器解析流程:

    • HTML解析器(HTMLParser)优先运行,它会解码HTML实体,从而恢复JavaScript代码中的特殊字符。
    • JavaScript解析器(JavaScript Parser)随后执行,此时JavaScript代码已经被“修复”成可以执行的形式。

    因此,最终执行的JavaScript为:

    1
    2
    3
    javascript
    Copy code
    alert('');alert('2');

    这实现了攻击者的目的:在页面中注入了额外的JavaScript代码。

各种场景下的XSS防御

想要根治XSS问题,可以列出所有XSS可能发生的场景,再一一解决。

下面用变量“$var”表示用户数据,它将被填充入HTML代码中。

可能存在以下场景:

在HTML标签中输出

  • 例子

    1
    2
    <div>$var</div>
    <a href=# >$var</a>
  • 利用方式

    一般是构造一个<script>标签,或者是任何能够产生脚本执行的方式。比如

    1
    <div><script>alert(/xss/)</script></div>

    或者

    1
    <a href=# ><img src=# onerror=alert(1) /></a>
  • 防御方法

    对变量使用HtmlEncode。

在HTML属性中输出

  • 例子

    1
    <div id="abc" name="$var" ></div>
  • 利用方式

    使用 " 闭合属性引号

    1
    <div id="abc" name=""><script>alert(/xss/)</script><"" ></div>
  • 防御方法

    HtmlEncode。

在script标签中输出

  • 例子

    1
    2
    3
    <script>
    var x = "$var";
    </script>
  • 利用方式

    使用 " 闭合引号

    1
    2
    3
    <script>
    var x = "";alert(/xss/);//";
    </script>
  • 防御方法

    JavascriptEncode

在事件中输出

  • 例子

    1
    <a href=# onclick="funcA('$var')" >test</a>
  • 利用方式

    闭合引号

    1
    <a href=# onclick="funcA('');alert(/xss/);//')" >test</a>
  • 防御方法

    JavascriptEncode

在CSS中输出

  • 例子与利用方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 通过@import指令从恶意URL导入一个CSS文件
    <STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>
    # 从指定的URL加载绑定文件,该文件可以包含恶意的XBL
    <STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE>
    # behavior属性用于将特定的行为(通常是HTC文件,一种微软专有的HTML组件)附加到页面元素。
    <XSS STYLE="behavior: url(xss.htc);">
    # 利用CSS中的list-style-image属性,插入JavaScript代码,该代码在渲染列表项图像时执行。
    <STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS
    # 通过background-image属性插入JavaScript代码。
    <DIV STYLE="background-image: url(javascript:alert('XSS'))">
    # expression是一个允许CSS属性值为JavaScript表达式的特性。这可以用来执行JavaScript代码。
    <DIV STYLE="width: expression(alert('XSS'));">
  • 防御方法

    尽可能地禁止用户控制的变量在<style>标签、HTML标签的style属性以及CSS文件中输出。

    可以使用encodeForCSS()函数

在地址中输出

  • 例子

    1
    <a href="http://www.evil.com/?test=$var" >test</a>
  • 利用方式

    一个url的组成一般如下:

    1
    [Protocal][Host][Path][Search][Hash]

    如:

    1
    2
    3
    4
    5
    6
    https://www.a.com/a/b/c/test?abc=123#sss
    [Protocal]="https://"
    [Host]="www.a.com"
    [Path]="/a/b/c/test"
    [Search]="?abc=123"
    [Hash]="#ssss"

    攻击者可能会构造伪协议实施攻击,包括javascript、vbscript、dataURI等

    1
    <a href="javascript:alert(1);" >test</a>

    闭合引号

    1
    <a href=# onclick="funcA('');alert(/xss/);//')" >test</a>
  • 防御方法

    • 检查URL的协议:确保URL以“http”或“https”开头,如果不是,则自动添加。

    • URLEncode路径和参数:在确保协议和主机部分正确后,再对路径和参数部分进行URLEncode。

      在用户能够完全控制URL的情况下,Protocal和Host部分不能使用URLEncode,否则会改变URL的语义。

富文本处理

什么是富文本:

1
2
3
有些时候,网站需要允许用户提交一些自定义的HTML代码,称之为“富文本”。
比如一个用户在论坛里发帖,帖子的内容里要有图片、视频,表格等,
这些“富文本”的效果都需要通过HTML代码来实现。

如何处理富文本:

  • 通过htmlparser可以解析出HTML代码的标签、标签属性和事件。
  • 禁止事件
  • 标签的选择:使用白名单,只允许<a><img><div>等比较“安全”的标签存在。
  • 自定义CSS:尽量禁止

DOM Based XSS 防御

前文提到的方法都是针对 “从服务器应用直接输出到HTML页面” 的XSS漏洞,并不适用于DOM Based XSS。

DOM Based XSS漏洞发生在客户端,浏览器在解析和执行JavaScript代码时引入了恶意脚本。

  • 例子1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script>
    function test(){
    var str = document.getElementById("text").value;
    //将 div 元素 (id="t") 的内部 HTML 设置为一个带有 str 作为链接地址的 <a> 元素。链接的文本显示为 testLink。
    document.getElementById("t").innerHTML = "<a href='"+str+"' >testLink</a>";
    }
    </script>

    <div id="t" ></div>
    <input type="text" id="text" value="" />
    <input type="button" id="s" value="write" onclick="test()" />

    在button的onclick事件中,执行了test()函数,而该函数将text标签的输入值写入了DOM节点,最后导致了XSS的发生。

  • 例子2

    1
    2
    3
    4
    5
    //code1 
    <script>
    var x="$var";
    document.write("<a href='"+x+"'>test</a>");
    </script>

    变量 $var 被写入html页面中,可能产生xss攻击。

    假设用户输入 $var 的值为onclick=alert(1);//,这个值被发送给了服务器端,服务器对其进行了 javascriptEscape 转义处理

    1
    2
    3
    4
    5
    //code2
    <script>
    var x="\x20\x27onclick\x3dalert\x281\x29\x3b\x2f\x2f\x27";
    document.write{"<a href='"+x+"'>test</a>"};
    </script>

    转义后的值被嵌入到HTML模板中,并传递到客户端浏览器。

    但是,这个转义是在服务器端完成的,目的是防止直接在服务器端生成的HTML中执行恶意代码。然而,转义后的字符串被传递到客户端后,浏览器会重新解析这些转义字符,使其恢复原始的恶意代码。

    当浏览器解析并执行这段JavaScript代码的时候,浏览器重新渲染页面,转义后的结果在 document.write 执行时,浏览器会将转义字符还原为:

    1
    <a href='' onclick=alert(1);//''>test</a>

    点击 test 链接会触发 onclick 事件,成功注入XSS。

    为什么会出现这样的转义无效现象??

    首先,客户端对x的转义在javascript中生效了

    1
    var x="\x20\x27onclick\x3dalert\x281\x29\x3b\x2f\x2f\x27";			(1)

    当这段代码执行的时候,JavaScript解释器将这些转义字符解析为对应的实际字符

    1
    var x = " 'onclick=alert(1);//' ";									(2)

    注意,这里因为转义的存在,这段javascript代码中将x的内容'onclick=alert(1);//' 视作了字符串,并不会对其进行执行

    但是后续document.write执行的时候,会将 x 的内容(已经完成解析,即为上面(2)中的内容)插入到HTML中,而这段在html上插入时没有进行再转义,产生了xss漏洞。

  • 防御方法

    JS输出到HTML页面,也相当于一次XSS输出过程。需要分语境使用不同的编码函数。

    • 首先,当”$var”输出到<script>时,应该执行一次javascriptEncode

      这一步是在将用户输入赋值给 JavaScript 变量时进行的编码,确保输入不会被解释为 JavaScript 代码。

      在服务器端和客户端都可以进行编码

    • 其次,document.write输出到HTML,要分2种情况

      • 如果document.write输出到事件或者脚本,再做一次javascriptEncode
      • 如果document.write输出到HTML内容或者属性,则做一次HtmlEncode
  • DOM based XSS的触发点

    • JS输出到HTML页面

      需要重点关注这几个地方的参数是否可以被用户控制

      1
      2
      3
      4
      5
      6
      7
      8
      9
      document.write()
      document.writeln()
      xxx.innerHTML=
      xxx.outerHTML=
      innerHTML.replace
      document.attachEvent()
      window.attachEvent()
      document.location.replace()
      document.location.assign()
    • 其他

      1
      2
      3
      4
      5
      6
      7
      页面中所有的inputs框
      window.location(href、hash等)
      window.name
      document.referrer
      document.cookie
      localstorage
      XMLHttpRequest返回的数据