注入:當輸入變成了程式碼
注入是最古老、至今仍最常見的嚴重漏洞。它發生在使用者提供的資料被當成指令而不是資料來處理的時候。對大多數應用來說,有兩種型別值得關注。
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 時,總得有東西去轉義它或引數化它。