XSS存储型攻击案例,留言板漏洞演示
存储型 XSS 和反射型最大的区别在于:恶意脚本会先被保存到服务器数据库,之后每个访问该页面的用户都会中招,危害范围更大。本文用 PHP 加 MySQL 写一个极简留言板,完整演示存储型 XSS 的植入、触发和修复过程,零基础也能跟着复现。
## 测试环境与漏洞代码
准备一台 Linux 服务器或本地 PHP 环境,要求 PHP 7.x 以上、MySQL 5.7 以上。这里用宝塔面板搭建,路径为:网站 → 添加站点 → 创建数据库。
建表语句如下,存留言内容时不加任何转义:
```sql
CREATE TABLE messages (
id INT AUTO_INCREMENT PRIMARY KEY,
nickname VARCHAR(50),
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
留言提交和展示的 PHP 代码故意省略过滤逻辑:
```php
// save.php
$nick = $_POST['nickname'];
$msg = $_POST['content'];
$sql = "INSERT INTO messages (nickname, content) VALUES ('$nick', '$msg')";
// 直接拼接执行,无转义
// list.php
$res = mysqli_query($conn, "SELECT * FROM messages ORDER BY id DESC");
while ($row = mysqli_fetch_assoc($res)) {
echo "
{$row['nickname']}: {$row['content']}
";
}
```
这段代码的问题很明确:输入未过滤、输出未转义,数据库里存什么,浏览器就执行什么。
## 攻击复现:从提交到触发
打开留言板页面,在“留言内容”框中填入一段脚本:
```html
```
点击提交后,查看数据库:
```sql
SELECT content FROM messages WHERE id = 1;
```
会看到脚本原样存储。此时刷新留言列表页面,浏览器会直接弹出 `alert` 窗口。更危险的写法是窃取 Cookie:
```html
```
只要管理员访问留言板,会话 Cookie 就可能被发送到攻击者服务器。**存储型 XSS 的核心危害是:脚本持久化在数据库中,所有浏览该页面的用户都会被动执行,无需诱导点击。**
## 修复方案:输入过滤与输出转义
修复要同时做两件事:入库前过滤危险标签,输出时按 HTML 实体转义。
入库阶段,用 `htmlspecialchars` 对内容做基础转义,或者用白名单过滤:
```php
$msg = htmlspecialchars($_POST['content'], ENT_QUOTES, 'UTF-8');
$stmt = $conn->prepare("INSERT INTO messages (nickname, content) VALUES (?, ?)");
$stmt->bind_param("ss", $nick, $msg);
$stmt->execute();
```
输出阶段同样不能省:
```php
echo htmlspecialchars($row['content'], ENT_QUOTES, 'UTF-8');
```
如果业务允许富文本,就不要用简单转义,改用 HTMLPurifier 这类白名单库,只放行 `p`、`a`、`strong` 等安全标签。
**判断是否修复成功的标准:再次提交 ``,页面应显示为纯文本,而不是弹出窗口。**
## 常见踩坑点
- 只在输入时转义、输出时直接打印,仍然可能被绕过。
- 使用 `strip_tags` 删除标签,但属性中的 `onerror`、`onclick` 事件未处理。
- 数据库字段用 `TEXT` 却只过滤了 `GET` 请求,忽略 `POST` 提交。
- 修复后未清理历史脏数据,已存储的恶意脚本仍会执行。
## 验证与后续加固