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 标头以限制内联 `