注入:当输入变成了代码
注入是最古老、至今仍最常见的严重漏洞。它发生在用户提供的数据被当成指令而不是数据来处理的时候。对大多数应用来说,有两种类型值得关注。
injection(SQL 注入)——SQL(Structured Query Language,结构化查询语言)是应用用来向数据库索要数据的语言——是用户输入混进了数据库查询。经典的脆弱写法:
// VULNERABLE: 用户的输入被直接粘进了查询里
app.get("/user", (req, res) => {
const name = req.query.name;
db.query(`SELECT * FROM users WHERE name = '${name}'`);
});
// 如果有人传入 name = '; DROP TABLE users; --
// 你的查询就变成了一条删表的命令。
修复办法是用参数化查询(也叫预编译语句)。你把查询和数据分开发送,这样数据库就永远不会把其中一个错当成另一个:
// SAFE: 值作为参数传入,永远不作为代码
app.get("/user", (req, res) => {
const name = req.query.name;
db.query("SELECT * FROM users WHERE name = ?", [name]);
});
规则是:永远不要用字符串拼接来构造查询。 如果你在 diff 里看到反引号或 + 把 SQL 拼在某个变量周围,停下来,要求改成参数化的版本。
两条路都把同一份用户输入送进数据库,但只有一条能让数据库不把那份输入误当成命令:
用户输入: '; DROP TABLE users; --
│
├─────────────────────────┬───────────────────────────┐
▼ ▼
✗ 字符串拼接 ✓ 参数化查询
"...WHERE name= "...WHERE name = ?", [input]
'" + input + "'" (查询与数据分开发送)
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ 数据库 │ │ 数据库 │
│把输入当作 │ │仅把输入当作 │
│命令执行 │ │数据处理 │
│ → 表被删除 │ │ → 安全查询 │
└───────────────┘ └───────────────┘
泄露 安全
XSS(cross-site scripting,跨站脚本) 是同一个思路在浏览器里的版本。如果你拿用户输入、把它当作原始 HTML(HyperText Markup Language,超文本标记语言,告诉浏览器该显示什么的代码)塞进页面,攻击者就能注入一个 <script> 标签,让它在你其他用户的浏览器里运行——比如窃取他们的会话。修复办法是转义:把用户内容当作文本来渲染,而不是 HTML。像 React 这样的现代框架默认会转义,这很棒——直到 AI 为了“让它能跑”伸手去用 dangerouslySetInnerHTML 或 innerHTML 这种逃生口。这些会绕过保护。在 diff 里看到任何这类调用,都要当成需要质疑的东西。
每个注入漏洞背后统一的原则是:让数据和代码保持分离。 每当用户输入要进入一个查询、一条命令、一个模板或 HTML 时,总得有东西去转义它或参数化它。