萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

Node.js MySQL 防止 SQL 注入的四種核心姿勢 (mysqljs/mysql)

SQL 注入是 Web 應用最頂級的安全風險之一。在 Node.js 使用 mysqljs/mysql 驅動操作 MySQL 時,**絕對禁止字符串拼接 SQL**,必須使用驅動內建的參數化/轉義機制。本文系統總結四種核心防注入姿勢,附代碼示例與轉義規則詳解。

核心原則:參數化查詢

驅動內部會將參數進行**上下文感知的轉義**,生成安全的 SQL 文本發送給服務端,徹底切斷注入路徑。

姿勢一:使用 escape() 手動轉義參數

適用於動態構建 SQL 片段、IN 列表等場景。

const mysql = require('mysql');
const connection = mysql.createConnection({...});

const userId = 1;
const name = "test'; DROP TABLE users; --";  // 惡意輸入

// 三種調用方式等價
const sql1 = 'SELECT * FROM users WHERE id = ' + connection.escape(userId) + ' AND name = ' + connection.escape(name);
const sql2 = 'SELECT * FROM users WHERE id = ' + mysql.escape(userId) + ' AND name = ' + mysql.escape(name);
const sql3 = 'SELECT * FROM users WHERE id = ' + pool.escape(userId) + ' AND name = ' + pool.escape(name);

connection.query(sql1, (err, results) => { ... });
// 生成安全 SQL: SELECT * FROM users WHERE id = 1 AND name = 'test\';\ DROP TABLE users; --'

escape() 轉義規則詳解:

輸入類型轉義結果
Number原樣輸出 (不加引號)
Booleantrue / false
Date'YYYY-MM-DD HH:mm:ss' (MySQL DATETIME 格式)
Buffer十六進制字面量 X'0fa5'
String安全轉義:單引號→\'、反斜線→\\、控制字符轉義
Array轉為列表 'a', 'b' (適合 IN (...))
多維數組轉為分組列表 ('a', 'b'), ('c', 'd') (適合 VALUES (...), (...))
Object轉為 key=value 鍵值對 (適合 SET/WHERE),嵌套對象轉為 JSON 字符串
undefined / nullNULL
NaN / Infinity**觸發 MySQL 錯誤** (不支援)

姿勢二:使用 ? 佔位符 (推薦標準做法)

connection.query() 內部自動調用 escape(),代碼最簡潔、可讀性最佳。

const userId = 1;
const name = 'test';

// 陣列佔位符 (順序對應)
connection.query('SELECT * FROM users WHERE id = ? AND name = ?', [userId, name], (err, results) => { ... });

// 物件佔位符 (鍵值對自動展開為 `key = ?`,極其適合 INSERT/UPDATE)
const post = { userId: 1, name: 'test', email: 'test@example.com' };
connection.query('INSERT INTO posts SET ?', post, (err, result) => { ... });
// 生成: INSERT INTO posts SET `userId` = 1, `name` = 'test', `email` = 'test@example.com'

// WHERE 子句物件用法
connection.query('SELECT * FROM users WHERE ?', { status: 1, role: 'admin' }, (err, rows) => { ... });
// 生成: SELECT * FROM users WHERE `status` = 1 AND `role` = 'admin'

姿勢三:使用 escapeId() 轉義標識符 (表名/列名/庫名)

**參數化查詢無法處理標識符** (如動態表名、動態排序列)。若標識符來自用戶輸入,必須用 escapeId() 包裹反引號並轉義內部反引號。

const sortColumn = 'created_at'; // 或來自用戶輸入 'created_at; DROP TABLE users; --'
const direction = 'DESC';

// 錯誤做法: sql = 'SELECT * FROM posts ORDER BY ' + sortColumn + ' ' + direction;
// 正確做法:
const sql = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sortColumn) + ' ' + connection.escape(direction);
// 生成: SELECT * FROM posts ORDER BY `created_at` 'DESC'  (direction 仍需 escape 為字符串)

// 三種調用方式
mysql.escapeId(identifier);
connection.escapeId(identifier);
pool.escapeId(identifier);

姿勢四:使用 mysql.format() 準備查詢 (高級模板)

類似 printf 風格,支援 ?? (標識符佔位符) 和 ? (值佔位符),自動選擇合適轉義方法。

const table = 'users';
const column = 'id';
const value = 1;

const sql = "SELECT * FROM ?? WHERE ?? = ?";
const inserts = [table, column, value];

const formattedSql = mysql.format(sql, inserts);
// 生成: SELECT * FROM `users` WHERE `id` = 1

// 直接執行
connection.query(formattedSql, (err, results) => { ... });

// 或結合 query 直接使用 (內部會調用 format)
connection.query(sql, inserts, (err, results) => { ... });

五、 實戰最佳實踐清單

  1. 默認使用姿勢二 (?): 95% 以上場景覆蓋,代碼清晰、安全、可維護。
  2. 動態標識符 (表/列/排序) 用姿勢三 (escapeId): 絕不拼接標識符。
  3. 複雜動態構建 (如動態 WHERE 條件數量不定) 用姿勢一: 手動 escape() 拼接,或使用查詢構建器 (Knex.js, TypeORM, Sequelize) 進一步抽象。
  4. 批量插入用姿勢四 (?? + ?) 或多維數組 escape(): 高效生成 VALUES (?,?), (?,?)。
  5. 嚴禁: connection.query('SELECT * FROM ' + userInputTable)、connection.query('SELECT * FROM users WHERE name = \'' + userInput + '\'')。
  6. 啟用連接池: mysql.createPool() 復用連接,提升性能並統一配置。
  7. 類型安全: 使用 TypeScript + 類型定義 (@types/mysql),編譯期捕獲參數類型錯誤。
  8. 審計日志: 生產環境記錄執行 SQL (脫敏後) 便於事後追蹤。

六、 總結

Node.js MySQL 防注入的核心是 **「數據與代碼分離」**。mysqljs/mysql 提供了完備的轉義工具鏈:

  • 值參數化: ? 佔位符 (首選) / escape() / format()
  • 標識符轉義: escapeId() / format() 的 ??

將這四種姿勢內化為肌肉記憶,配合 Code Review 機制攔截任何字符串拼接 SQL,構建起堅實的應用層安全防線。

copyright © 萬盛學電腦網 all rights reserved