'XSS-Challenge'

Level 1

通过GET方式传入name,会在页面上显示出来

写入payload

1
2
3
4
5
<?php 
ini_set("display_errors", 0);
$str = $_GET["name"];
echo "<h2 align=center>欢迎用户".$str."</h2>";
?>

在代码中没有看到过滤、转义,直接将输入显示在页面上

Level 2

通过GET方式传入keyword,会在页面上显示出来

并且在<form>标签的value属性上也会显示输入

尝试写入payload,发现没有正确的显示出来

使用Firebug查看,发现<h2>标签中的输入被HTML实体化编码

<form>标签中value属性的值同样被HTML实体化,不过由于在标签内部,所以可以使用事件触发xss,而不需要加上会被转义的<>

payload

1
2
3
4
5
6
7
8
9
10
<?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>';
?>

查看代码,可以看到htmlspecialchars()对输入进行了转义

Level 3

通过GET方式传入keyword,显示方式和Level 2相同,在<h2><form>标签上显示

输入Level 2的payload,发现没有成功闭合

使用'代替",成功闭合

payload

1
2
3
4
5
6
7
8
9
10
<?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>";
?>

代码上和第二关不同的地方在于,value=属性后引号变成了单引号,所以需要使用'进行闭合。由于浏览器会将'变为"显示,所以使用Firebug审查元素时,无法区分

Level 4

通过GET方式传入keyword,显示方式和Level 2略有不同,在<h2>标签上显示和输入的是相同的,不过依然被HTML实体化,而在<form>标签上显示的没有<>

使用Level 2的payload

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace(">","",$str);
$str3=str_replace("<","",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level4.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

可以看到str3是被过滤掉<>的,不过由于使用的on事件,而且本身在标签内部,所以不需要使用<>

Level 5

测试显示效果

使用Level 2的payload

on事件被过滤为o_n O_o

使用javascript:alert(/xss/)绕过

payload

1
2
3
4
5
6
7
8
9
10
11
12
<?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>';
?>

查看代码,发现不仅过滤了on,还过滤了<script,不过str3没有进行HTML实体化转义

Level 6

测试显示效果

两个输出点的<>都被HTML实体编码

测试on事件

使用"闭合,on被过滤为o_n

测试javascript:alert()

这里href也被过滤了,变成了hr_ef。有个奇怪的地方,刚才测试显示效果时<>被转义掉,但这次并没有被转义掉

被过滤掉的还有src、script

然后发现大小写可以绕过,明明前面都不能绕过的

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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>';
?>

查看代码发现<scriptonsrcdatahref都被过滤,但是并没有strtolower()函数过滤大小写

Level 7

测试显示效果

这次使用带<Chessur>测试显示效果,发现变成小写了

测试on事件

发现下面的on被过滤为空

尝试双写绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
ini_set("display_errors", 0);
$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);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level7.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>

查看代码,这一关又有大小写过滤了….
将关键词替换为空,所以双写直接绕过

Level 8

看起来像DOM型XSS

测试显示效果

这次有2个输出点,一个是<form>标签里,一个是在<a>标签里

查看过滤情况

发现on、script、src都被过滤掉了,而且大小写无法绕过

由于输出点在href属性中,所以使用<a href=javascript:alert()</a>使用&#x0A;来绕过过滤

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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('"','&quot',$str6);
echo '<center>
<form action=level8.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>

查看代码,scriptonsrcdatahref"都被过滤掉了

Level 9

测试显示效果

使用了新的测试payload

1
";!–'<CheSSur>=&{(OnonSrcsrcscriptScRIPt)}

大小写转为小写,没有过滤,有HTML实体编码转义
href标签处会判断链接是否合法
判断链接是否合法的规则是输入中是否有http://

使用Level 8的payload加上//http://

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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('"','&quot',$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>';
}
?>

查看代码发现和Level 8基本相同,在下面添加了判断URL是否合法的规则,不过由于使用的是strpos()函数,只判断字符串中是否出现过字符,而没有判断出现位置,所以可以添加到行尾,使用注释符注释掉进行绕过

Level 10

测试显示效果

进行了HTML实体化编码转义,对on、src、script进行了过滤

但是在<h2标签下面有隐藏的<form>标签,测试发现只有t_sort可以被更改type显示出来

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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>';
?>

查看代码,发现t_sort接收从页面传来的值,过滤掉了<>,使用on事件绕过

Level 11

测试显示效果

和Level 10类似,都有隐藏的<form标签

从Level 10跳转来会看到Referer,直接打开并没有

使用FireFox浏览器无法修改为Payload,用BurpSuite抓包修改Referer

Forward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php 
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>';
?>

查看代码

$str11获取Referer,过滤掉了<>,再输出到t_ref

Level 12

直接用Firebug审查元素

看到了<input name="t_ua" value="Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0" type="hidden">

用BurpSuite抓包修改UserAgent

Forward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php 
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>';
?>

查看代码

$str11获取User Agent,过滤掉<>再输出到t_ua

Level 13

Firebug审查元素

看到t_cook

使用BurpSuite抓包

Forward

可以看到t_cook的值是Cookie中user的值

抓包修改Cookie

Forward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
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>';
?>

$str11获取Cookie中的user,过滤掉<>再输出到t_cook

Level 14

使用Firebug审查元素

发现iframe标签,连接到http://www.exifviewer.org

以为要通过注入修改iframe标签,但查了一下是要上传xss图片到这个网页上,解析之后弹窗

开了纸飞机之后,页面加载成功

恶意图片

上传图片页面

Upload

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>欢迎来到level14</title>
</head>
<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>
</html>

查看代码没有任何输入点,只能访问http://www.exifviewer.org

原理是修改图片的exif信息,在解析器解析图片exif时触发XSS。

未加载的图片都是XSSpayload

Level 15

测试显示效果

好像没有输出…

页面加载了一个JavaScript脚本

在页面源代码里看到输出点

查了一下ng-include:,发现可以包含一个页面

包含一下第一关的Payload

1
2
3
4
5
<?php 
ini_set("display_errors", 0);
$str = $_GET["src"];
echo '<body><span class="ng-include:'.htmlspecialchars($str).'"></span></body>';
?>

好像Firebug对未加载的标签并不显示,因为没有1.gif这个文件,所以只能在源代码里查看

ng-include包含的文件需要用引号括起来

Level 16

测试显示效果

1
";!–'<CheSSurOnonSrcsrcscriptScRIPt>=&{()}

script被过滤为空格,空格被转义为&nbsp;,大小写被过滤,srcon未被过滤,用<img>标签测试

发现/也被过滤成空格

使用%0a%0c%0d绕过空格检测

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","&nbsp;",$str);
$str3=str_replace(" ","&nbsp;",$str2);
$str4=str_replace("/","&nbsp;",$str3);
$str5=str_replace(" ","&nbsp;",$str4);
echo "<center>".$str5."</center>";
?>
<center><img src=level16.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str5)."</h3>";
?>

查看代码,script/都被过滤为&nbsp;

Level 17

测试显示效果

闭合src

1
2
3
4
<?php
ini_set("display_errors", 0);
echo "<embed src=xsf01.swf?".htmlspecialchars($_GET["arg01"])."=".htmlspecialchars($_GET["arg02"])." width=100% heigth=100%>";
?>

查看代码,发现2个变量都被HTML实体化编码转义,使用on事件可以绕过

Level 18

测试显示效果

闭合src

1
2
3
4
<?php
ini_set("display_errors", 0);
echo "<embed src=xsf02.swf?".htmlspecialchars($_GET["arg01"])."=".htmlspecialchars($_GET["arg02"])." width=100% heigth=100%>";
?>

查看代码,发现和Level 17的代码区别在于引用了不同的文件,在过滤上并没有改动,所以Level 17的Payload依然可以用

这两关看起来像Flash XSS,但其实只是简单的注入

Level 19

这关真的是考察Flash XSS,并不懂怎么反编译flash,而且现在flash用的也少了,学习的价值并不高,忘记在哪里看到一句话

两年内用不到的知识,并不需要去学习。
两年后会被淘汰的知识,也不需要去学习。

这里只放上Payload From:XSS挑战之旅–游戏闯关

1
?arg01=version&arg02=<a href="javascript:alert(1)">123</a>

Level 20

同Level 19

只放上Payload From:XSS挑战之旅–游戏闯关

1
?arg01=id&arg02=\%22))}catch(e){}if(!self.a)self.a=!alert(1)//%26width%26height

总结

1.使用on事件绕过<>的转义 Level 2
2.使用javascript:alert()绕过对on事件的过滤 Level 5
3.测试不应该跳过任何一步,XSS挑战的难度设计并不是线性增加的,有时上一关无效的payload,可能下一关就有用了 Level 6
4.输出点在<a>标签中,可以使用<a href=javascript:alert()</a>绕过 Level 8
5.使用&#x0A;等制表符来绕过过滤 Level 8
6.判断输入是否合法,可以根据函数特性绕过,如strpos()函数 Level 9
7.查看源码是个好习惯,希望我有 Level 10
8.修改referer、user agent、cookie注入xsspayload Level 11,Level 12,Level 13
9.有时Firebug不会显示某些元素,还是要多看源码 Level 15
10.ng-include包含的文件需要用引号括起来 Level 15
11.使用%0a%0c%0d绕过空格检测 Level 16
12.看起来像A,但要试过才知道是不是 Level 17 Level 18

感受

XSS Challenge并不像Sqli-Lab难度是递增的,有的关卡使用相同Payload都可以过去,不过还是学到了很多新的姿势。感谢作者做了这个挑战,也感谢各个写了WriteUp的大佬,学到很多新的姿势。

参考

[1] 某xss挑战赛闯关笔记

[2] XSS挑战之旅–游戏闯关