XSS-labs靶场实战:从隐藏输入到HTTP头注入的漏洞挖掘与绕过
1. 项目概述:XSS-labs靶场通关实战
如果你刚开始接触Web安全,想找一个地方系统地练习跨站脚本攻击,那XSS-labs这个靶场绝对是你的不二之选。它不像一些大型综合靶场那样功能庞杂,而是专注于XSS这一种漏洞类型,从最基础的反射型到各种绕过过滤的场景,关卡设计由浅入深,非常适合新手用来建立对XSS漏洞的直观理解和攻击手感。
最近我带着几个刚入门的朋友,一起把XSS-labs靶场的第10关到第13关给打通了。这几关开始引入了一些新的过滤和限制条件,比如对script标签的检查、对事件处理器的过滤,以及<input>标签的隐藏属性限制,正是从“知道怎么弹窗”到“理解如何绕过”的关键转折点。网上虽然有不少通关攻略,但很多只给了payload,没讲清楚背后的逻辑和试错过程。这篇内容,我就结合我们实际通关的踩坑经历,把每一关的解题思路、payload构造的思考过程,以及那些容易被忽略的细节,给你掰开揉碎了讲清楚。目标很简单:让你不仅能复现通关,更能明白每一步为什么要这么做,下次遇到类似的防御机制,自己能独立想出办法。
2. 环境准备与靶场搭建
2.1 靶场获取与部署
XSS-labs靶场通常是一个PHP编写的Web应用,你需要一个能运行PHP的环境来部署它。最常见的选择是使用集成环境,比如XAMPP、PHPStudy或者Docker。这里我以PHPStudy为例,因为它对Windows用户非常友好,图形化界面操作简单。
首先,去XSS-labs的开源仓库(例如在GitHub上搜索“xss-labs”)下载最新的源码包。下载后,你会得到一个包含多个PHP文件的文件夹。将这个文件夹整个复制到你的Web服务器根目录下。对于PHPStudy来说,根目录通常是phpstudy_pro/WWW/。复制完成后,启动PHPStudy的Apache和MySQL服务(虽然XSS-labs可能不依赖数据库,但启动MySQL是个好习惯)。
接着,打开浏览器,访问http://localhost/xss-labs/(假设你的文件夹名是xss-labs)。如果页面正常显示,并且能看到关卡列表,说明环境部署成功。如果遇到问题,最常见的原因是PHP版本不兼容。XSS-labs是一个比较老的靶场,建议使用PHP 5.x版本(如PHP 5.4或5.6)来运行,高版本PHP可能会因为某些函数被弃用而报错。在PHPStudy中,你可以很方便地切换PHP版本。
注意:永远在本地或授权的虚拟环境中进行安全测试。不要在任何未授权的公网系统上尝试这些技术,这是法律和道德的底线。
2.2 必备工具与浏览器设置
工欲善其事,必先利其器。除了靶场本身,你还需要几样趁手的工具。
浏览器与开发者工具:现代浏览器(Chrome、Firefox、Edge)自带的开发者工具是安全测试的瑞士军刀。你需要熟练使用“元素”(Elements)面板来查看和修改页面HTML结构,用“控制台”(Console)面板来查看JavaScript错误和执行命令,用“网络”(Network)面板来观察HTTP请求和响应的原始数据。特别是查看响应,能帮你确认服务器到底返回了什么,过滤规则是否生效。
Burp Suite:这是一个专业的Web安全测试平台。对于XSS测试,它的“代理”(Proxy)和“重放”(Repeater)功能极其有用。通过设置浏览器代理,你可以拦截、查看和修改所有发往靶场的HTTP请求,方便你精细地构造和测试payload。社区版(免费)的功能就足够我们完成这个靶场。
一个简单的文本编辑器:用来记录和整理你的payload、思考过程和测试结果。好记性不如烂笔头。
在开始测试前,建议暂时关闭浏览器的XSS过滤器(如Chrome的XSS Auditor,虽然新版已移除,但Edge等可能仍有类似功能),或者将其设置为“仅报告”模式,避免浏览器的内置防护机制干扰你对漏洞真实存在性的判断。同时,确保你的Burp Suite代理设置正确,并能正常拦截流量。
3. 第十关:隐藏输入与参数污染
3.1 关卡界面与初步侦查
打开第十关,页面看起来非常简单,只有一个搜索框(<input>)和一个提交按钮。查看网页源代码,你会发现关键信息:有三个隐藏的<input>标签,它们的name属性分别是t_link,t_history,t_sort。而页面上可见的搜索框,其name是keyword。
我们的第一反应通常是尝试在keyword参数里注入。提交test,URL变成?keyword=test。查看页面回显和源代码,发现test被原样输出在了页面某处,但似乎被做了HTML实体编码(比如<变成了<),直接插入<script>alert(1)</script>是无效的。
这时,思路需要转变。题目提示我们关注“隐藏”的输入。在HTML中,隐藏输入框(<input type=”hidden”>)的值虽然用户不可见,但会随着表单一起提交。服务器端可能会处理这些隐藏参数。我们如何测试服务器是否接收并处理了这些隐藏参数呢?使用Burp Suite拦截提交keyword=test的请求,然后手动在请求体中添加&t_link=test2&t_sort=test3,再转发请求。观察响应页面,仔细搜索test2和test3这两个字符串。
3.2 漏洞点定位与利用构造
通过Burp Repeater反复测试,你会发现t_sort这个参数的值被原封不动地输出在了页面的一个<input>标签的value属性里,而且没有任何过滤!格式大致是<input name=”t_sort” value=”我们提交的t_sort值”>。
这就为我们创造了经典的“属性值内XSS”的场景。我们的目标是闭合value属性的双引号,然后引入事件处理器或者新的标签。构造payload:” onmouseover=”alert(1)。这个payload的构成逻辑是:
- 开头的
”用于闭合value=”中的前引号。 - 接着一个空格,这是HTML属性之间的分隔符。
onmouseover=”alert(1)是一个新的事件处理器属性。当鼠标移动到这个输入框上时,就会触发执行alert(1)。- 注意,我们不需要闭合后面的引号,因为原始的HTML是
value=”…”,我们插入的内容成为了新的属性,后面的原始引号会被当作这个新属性值的结束引号。
所以,完整的利用过程是:在Burp Suite中拦截请求,将t_sort参数的值修改为” onmouseover=”alert(1),然后转发请求。页面加载后,将鼠标移动到那个隐藏输入框对应的位置(虽然不可见,但DOM元素存在),即可触发弹窗。
3.3 技巧与深度思考
这一关的核心教学点是“不要忽略任何可能的输入点”,包括前端不可见的隐藏字段。在实际的漏洞挖掘中,通过拦截修改请求来测试所有参数(包括GET、POST、Cookie)是一项基本功。
此外,我们利用了“属性值内”的XSS。这种场景下,<script>标签通常无法直接使用,因为它在属性值内部,不会被解析为标签。最有效的方式就是使用事件处理器属性,如onmouseover,onclick,onfocus,onerror(在img标签内)等。你也可以尝试构造”><script>alert(1)</script>来先闭合当前标签,再插入新标签,但前提是后端没有对尖括号进行过滤。在这一关,这种方法同样可行,它展示了另一种思路:跳出当前属性的束缚。
实操心得:在测试时,养成查看“页面源代码”而非仅“检查元素”的习惯。“检查元素”看到的是浏览器渲染并可能修改后的DOM树,而“页面源代码”是服务器返回的原始HTML,能更真实地反映后端处理结果,帮助你判断过滤发生的位置(是服务器过滤了,还是浏览器动态渲染的)。
4. 第十一关:HTTP请求头注入
4.1 关卡机制分析
第十一关的页面和第十关看起来几乎一样。同样有keyword和几个隐藏参数。我们尝试用第十关的方法,测试t_sort,发现不行了。服务器可能对t_sort进行了过滤或不再将其输出。
这时,需要扩大测试范围。题目名提示“HTTP请求头注入”,这意味着漏洞点可能不在常见的URL参数或表单体内,而在HTTP请求头中。哪些请求头是用户可控或相对容易操作的呢?User-Agent,Referer,Cookie是常见目标。
我们的测试方法是:用Burp Suite拦截一个普通请求(比如提交keyword=test),然后在Repeater模块中,依次修改这些请求头的值,加入一个特殊的测试字符串(例如testheader),然后观察响应页面中是否出现了这个字符串。
4.2 漏洞挖掘与利用链构建
经过测试,你会发现Referer请求头的值,被直接输出到了页面HTML中的一个<input>标签的value属性里。输出点的代码可能类似于<input type=”text” value=”来自Referer头的值”>。
这和第十关的场景一模一样:一个未经过滤的输出点位于HTML标签的属性值内。因此,利用方式也相同。构造payload:” onmouseover=”alert(1)。
在Burp Repeater中,将Referer请求头的值由原本的http://localhost/xss-labs/level11.php(或类似)修改为我们的payload:” onmouseover=”alert(1)。发送请求后,查看响应页面源代码,确认payload已被植入。然后在浏览器中访问该页面(或直接在Repeater的渲染视图里),将鼠标移动到那个输入框上,触发弹窗。
4.3 安全启示与拓展
这一关极具现实意义。很多开发者会仔细过滤来自URL和POST数据的用户输入,却容易忽略HTTP请求头,认为它们是浏览器自动生成、不可篡改的。实际上,通过代理工具,任何请求头都可以被轻易修改。
Referer头常用于日志记录、防盗链、来源分析等场景。如果记录后未经处理就直接输出到页面,就会形成存储型XSS的源头。类似的,User-Agent头也经常被记录用于统计客户端类型,也存在同样风险。
注意事项:在测试请求头注入时,要注意请求头的格式。例如,
Referer本身是一个URL,通常包含://等字符。我们的payload需要确保不破坏原始的大致格式,以免被某些简单的格式检查拦截。直接替换整个值为payload是最直接的方法。此外,Cookie也是高频攻击点,但通常用于窃取会话的利用,单纯弹窗的payload构造方式类似。
5. 第十二关:User-Agent头的攻防
5.1 界面分析与测试策略
第十二关延续了前两关的界面风格。有了第十一关的经验,我们直接猜测漏洞点可能在某个HTTP请求头。按照同样的流程进行测试:拦截请求,在Burp Repeater中修改User-Agent,Referer,Cookie等头,观察响应。
很快你会发现,User-Agent头的值被输出到了页面上。输出位置很可能也是在某个<input>标签的value属性里。尝试使用经典的” onmouseover=”alert(1)payload。
5.2 绕过过滤与payload变形
当你发送包含” onmouseover=”alert(1)的请求后,查看响应源代码,可能会发现payload并没有被完整植入,或者被修改了。例如,引号”或尖括号<>可能被删除或编码了。这说明服务器端对User-Agent这个输入点施加了过滤。
我们的任务变成了“绕过过滤”。首先需要探测过滤规则。这是一个试错的过程:
- 提交
”(一个双引号),看它是否被编码(变成")或删除。 - 提交
onmouseover,看这个关键词是否被过滤或删除。 - 提交
alert,看这个函数名是否被过滤。 - 尝试大小写变形,如
OnMouseOver、ONMOUSEOVER。因为HTML事件处理器属性名是大小写不敏感的,但后端过滤可能是大小写敏感的。 - 尝试使用HTML实体编码,如将
”写成",但注意这通常只在HTML文本上下文有效,在属性值内,浏览器会将其解码为字符后再解析属性。如果后端只过滤了原始字符而未过滤编码形式,这可能是一种绕过。 - 尝试使用JavaScript Unicode编码,如
alert(1)可以写成\u0061\u006c\u0065\u0072\u0074(1)。但这通常需要在<script>标签内或事件处理器值中才能被正确解码。
在这一关,经过测试,一种常见的有效绕过方式是使用大小写混合。例如,将payload构造为” oNmOuSeOvEr=”alert(1)。后端可能只过滤了全小写的onmouseover,而对大小写变体没有处理。
5.3 实战中的过滤测试方法论
这一关教会我们,面对过滤,不能轻易放弃,要有系统性的测试方法。
- 信息收集:首先确定输入点和输出点。
- 黑盒探测:向输入点提交一系列特殊字符和关键词(如
< > ‘ ” & ; ( ) javascript: onxxx alert confirm prompt),观察输出点的变化,是原样输出、被编码、被删除还是被替换。 - 规则归纳:根据观察结果,猜测后端的过滤规则是黑名单(禁止某些字符/词)还是白名单(只允许某些字符),是删除、替换还是编码。
- 尝试绕过:
- 大小写绕过:适用于对关键词进行简单字符串匹配的黑名单。
- 双写绕过:如果过滤是删除一次关键词,提交
oonnmouseover,过滤掉中间的onmouseover后可能剩下onmouseover。 - 插入无关字符:利用HTML和JS的解析特性,如
onmouseover(Tab键)=alert(1),某些解析器会忽略标签名/属性名中的控制字符。 - 编码绕过:尝试URL编码、HTML实体编码、Unicode编码等,取决于输出上下文和浏览器的解码顺序。
- 使用等价替代:不用
alert,用confirm或prompt;不用onmouseover,用onclick、onfocus、onerror等。
- 利用构造:找到能绕过过滤的payload后,精心构造完整的利用代码,确保语法正确并能被浏览器成功解析执行。
6. 第十三关:Cookie注入的利用
6.1 Cookie机制与漏洞原理
第十三关,页面依然简洁。有了前两关的经验,我们自然会测试Cookie请求头。Cookie是服务器发送到用户浏览器并保存在本地的一小块数据,会在浏览器下次向同一服务器再发起请求时被携带并发送。它常用于会话管理、个性化设置等。
如果服务器端将Cookie的值取出后,未经净化就直接嵌入到HTML页面中,就会造成反射型或存储型XSS。这一关模拟的就是这种场景。
使用Burp Suite拦截请求,观察原始的Cookie。通常靶场可能会设置一个名为user或level13的Cookie。我们在Repeater中修改这个Cookie的值,加入测试字符串。
6.2 利用过程与技巧
测试发现,某个Cookie的值(假设是user)被输出到了页面的<input>标签属性值内。我们尝试注入” onmouseover=”alert(1)。
然而,和第十二关类似,直接注入可能会失败,提示过滤。我们需要再次进行绕过测试。经过尝试,可能发现简单的”被过滤了。这时可以尝试使用HTML实体编码。但如前所述,在属性值内部,直接写"可能不会被浏览器在解析属性时解码为引号。
另一种思路是,既然过滤了引号,我们能否不用引号来闭合属性?在HTML中,属性值可以用单引号或双引号包裹,甚至在某些情况下可以不用引号(如果值不包含空格等特殊字符)。但事件处理器的值(alert(1))里包含括号,通常需要引号包裹。
我们可以尝试混合使用。例如,原始HTML是<input value=”COOKIE_VALUE”>。我们注入’ onmouseover=’alert(1)。这样,我们用单引号闭合了value属性,然后添加了事件处理器。但这里有个问题:事件处理器的值alert(1)我们也没有用引号包裹。这在HTML中是允许的,只要值不包含空格。alert(1)是合法的。所以最终构造的payload可能是:’ onmouseover=alert(1)。
这个payload的解析过程是:
- 原始:
<input value=”‘ onmouseover=alert(1)”>(假设我们注入的内容被放在双引号内)。 - 浏览器解析:
value属性的值就是字符串’ onmouseover=alert(1)。 - 由于字符串以单引号开始,浏览器会认为
value属性的值在第一个单引号处就结束了,即value=’‘(一个空字符串)。 - 接着,它看到了
onmouseover=alert(1),这被解析为一个新的属性。因为alert(1)没有引号包裹,浏览器会将其作为属性值。 - 当鼠标悬停时,浏览器会尝试执行字符串
alert(1),这正好是我们的JavaScript代码。
6.3 防御视角与总结
从第十关到第十三关,攻击面从显式的URL参数,扩展到隐藏表单字段,再到HTTP请求头(Referer, User-Agent),最后到Cookie。这清晰地展示了“一切用户输入皆不可信”的原则。输入可以来自HTTP请求的任何部分。
对于防御者而言,必须对所有来源的输入进行严格的验证和消毒。
- 输出编码:根据数据将要放置的上下文(HTML正文、HTML属性、JavaScript、URL、CSS),采用相应的编码方式。放在HTML属性值里,就要对
< > & ‘ ”等字符进行HTML实体编码。 - 内容安全策略(CSP):部署CSP可以有效地缓解XSS攻击,即使漏洞存在,也能限制脚本的执行。
- 使用安全框架:现代Web开发框架(如React, Vue, Angular)通常提供自动的上下文相关输出编码,但开发者仍需了解其原理,避免使用
dangerouslySetInnerHTML这类不安全API。
通关这四关,你应该已经掌握了在HTML属性值上下文进行XSS攻击的基本方法,以及面对简单过滤时的一些绕过思路。最重要的是建立了“寻找非常规输入点”和“系统性测试过滤规则”的思维模式。这些经验,将为你后续挑战更复杂的关卡和面对真实世界漏洞打下坚实的基础。
