viitheone/viilab
GitHub: viitheone/viilab
一个基于 Node.js 记事本应用的 Web 安全教学靶场,演示 OWASP Top 10 四种漏洞的利用与修复。
Stars: 0 | Forks: 0
# 🍉 ViiLab — 自定义漏洞 Web 应用程序
一个极简的记事本应用(Node.js/Express + SQLite),旨在演示来自 OWASP Top 10 的四种现实世界的 Web 漏洞;外加一个将其中两个漏洞组合成完全接管账户的组合攻击——每一种都提供了可用的漏洞利用和修复方案。
## 设置
```
npm install
node db/init.js # seeds the database
node server.js # runs on http://localhost:3000
```
内置账号:
| 用户名 | 密码 |
|----------|------------------|
| alice | alicepass123 |
| bob | bobpass456 |
| admin | S3cur3AdminPW! |
## 漏洞 1:SQL 注入 — 身份验证绕过
**位置:** `server.js` 中的 `POST /login`
**根本原因:** 登录查询使用原始字符串拼接构建,而不是使用参数化查询:
```
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
```
**漏洞利用:** 使用以下凭据登录:
- 用户名:`admin' -- `
- 密码:(任意)
`--` 注释掉了查询的其余部分,因此它变成了
`SELECT * FROM users WHERE username = 'admin' -- ' AND password = '...'`,
无论密码如何,它都会匹配到 admin 行。
**修复:** 使用参数化/预处理语句:
```
const user = db.prepare('SELECT * FROM users WHERE username = ? AND password = ?')
.get(username, password);
```
(密码也应该使用 bcrypt/argon2 进行哈希处理,切勿明文存储。)
## 漏洞 2:存储型跨站脚本攻击 (XSS)
**位置:** `views/note.ejs`,记事本内容字段
**根本原因:** 记事本内容使用 EJS 的非转义输出标签渲染:
```
<%- note.content %>
```
`<%-` 会跳过 HTML 转义。而 `<%=`(应用程序其余部分正确使用的标签)则会安全地对其进行转义。
**漏洞利用:** 创建一个包含以下内容的记事本:
```
```
该脚本会在任何查看该记事本的人(包括其他用户,如果与下文的 IDOR 结合使用;这就是存储型 XSS 在现实世界中成为会话劫持向量的方式)的浏览器中执行。
**修复:** 切换到 `<%= note.content %>` 以进行转义输出,或者在写入时使用 `sanitize-html` 等库清理输入(如果需要保留有限的格式)。
## 漏洞 3:访问控制失效 (IDOR)
**位置:** `server.js` 中的 `GET /notes/:id`
**根本原因:** 路由通过 ID 获取记事本,但未检查已登录用户是否确实拥有它:
```
const note = db.prepare('SELECT * FROM notes WHERE id = ?').get(req.params.id);
// no ownership check before rendering
```
**漏洞利用:** 以 alice 身份登录,然后直接访问 `/notes/3` 或 `/notes/4`;这些分别属于 bob 和 admin,并且它们的私人记事本内容(bob 的银行 PIN 码、admin 的服务器信息)在完全没有授权检查的情况下被返回。
**修复:** 在返回数据之前检查所有权:
```
if (note.owner_id !== req.session.user.id) {
return res.status(403).send('Forbidden');
}
```
## 漏洞 4:不安全的 Session Cookie 配置
**位置:** `server.js`,session 中间件配置
**根本原因:** Session Cookie 被显式设置为 `httpOnly: false`:
```
app.use(session({ ..., cookie: { httpOnly: false } }));
```
这意味着客户端 JavaScript(包括攻击者注入的脚本)可以读取 `document.cookie` 并查看 Session ID。如果在默认的 `httpOnly: true` 设置下,Cookie 对 JS 是完全不可见的。
**修复:** 移除此覆盖设置(或显式设置 `httpOnly: true`),并在通过 HTTPS 提供服务时添加 `secure: true`,这样 Cookie 在未加密的连接中也无法被读取。
## 组合攻击:存储型 XSS → Cookie 窃取 → Session 劫持 → 管理员账户接管
这是将漏洞 #2 和 #4 结合为真实账户接管路径的“那又怎样(核心影响)”,并展示了超越孤立漏洞搜寻的攻击者思维。
**场景:** `bob`(普通用户)想要管理员权限,但不知道管理员的密码。
1. Bob 向共享的**社区看板**(`/board` — 对每个已登录用户可见,包括管理员)发布了一个包含以下内容的记事本:
2. 当管理员登录并查看 `/board` 时,该脚本会在管理员的浏览器中执行。由于 Session Cookie 未设置 `httpOnly` 属性,脚本可以通过 `document.cookie` 读取它并将其发送到 `/steal`;这是一个模拟攻击者控制的数据收集服务器的端点。
3. Bob 检查 `/collector` 并获取管理员被盗的 Session Cookie。
4. Bob 将自己的 `connect.sid` Cookie 替换为被盗的 Cookie,然后请求 `/admin`。
5. 服务器无法区分管理员的真实浏览器和 Bob 伪造的请求;相同的有效 Session ID,相同的访问权限。Bob 现在正看着管理面板,而他从未触碰过管理员的密码。
**亲自运行完整的攻击链:**
```
node server.js &
bash exploit_chain.sh
```
**修复(纵深防御 — 以下任意一项均可打断此攻击链):**
- 对 `/board` 和 `/notes/:id` 的输出进行转义(修复 XSS 入口点)
- 在 Session Cookie 上设置 `httpOnly: true`(即使存在 XSS,脚本也无法读取它)
- 添加 CSP 标头以限制内联 `