~/VibeHandbook
免費 PDF

18 · 02

注入:當輸入變成了程式碼

注入是最古老、至今仍最常見的嚴重漏洞。它發生在使用者提供的資料被當成指令而不是資料來處理的時候。對大多數應用來說,有兩種型別值得關注。

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 為了“讓它能跑”伸手去用 dangerouslySetInnerHTMLinnerHTML 這種逃生口。這些會繞過保護。在 diff 裡看到任何這類呼叫,都要當成需要質疑的東西。

每個注入漏洞背後統一的原則是:讓資料和程式碼保持分離。 每當使用者輸入要進入一個查詢、一條命令、一個模板或 HTML 時,總得有東西去轉義它或引數化它。

想離線閱讀?

免費下載整本書的 PDF 或 EPUB。