LLM 程式碼生成中的 SQL 注入
LLM 如何透過字串格式化、不當參數化和 ORM 誤用生成 SQL 注入漏洞,以及偵測和預防策略。
概述
SQL 注入仍然是網路應用程式中最常見的漏洞類別之一(CWE-89),而 AI 程式碼生成工具使問題更加嚴重。Stanford 和其他機構的研究表明,使用 AI 程式碼助手的開發人員比不用 AI 輔助的開發人員產生更多安全漏洞,SQL 注入是最常引入的缺陷類型之一。
根本原因很直接:LLM 從網際網路上大量的程式碼語料庫學習,其中相當一部分程式碼使用字串格式化而非參數化查詢來執行 SQL 查詢。模型複製它見過最頻繁的模式,而不安全的模式在主導訓練資料的教學、Stack Overflow 答案和入門程式碼範例中往往更常見。
為什麼 LLM 生成 SQL 注入
訓練資料問題
不安全模式在訓練資料中出現更頻繁,原因如下:
- 教學優先考慮可讀性和簡單性而非安全性
- 快速範例和原型跳過參數化
- Stack Overflow 答案通常使用最簡單的方法
- 舊程式碼(在現代安全意識之前)使用字串格式化
LLM 不理解安全性——它根據模式預測最可能的下一個符元。當開發人員要求「寫一個按名稱查找用戶的函式」時,模型生成它見過最頻繁的模式。
常見 SQL 注入模式(SQL_INJECTION_PATTERNS)按嚴重性和頻率分類:
f_string_formatting(嚴重,非常常見):
- 不安全:
query = f"SELECT * FROM users WHERE name = '{username}'"然後db.execute(query) - 安全:
db.execute("SELECT * FROM users WHERE name = ?", (username,))
format_method(嚴重,常見):
- 不安全:
"SELECT * FROM products WHERE category = '{}' AND price >= {}".format(category, min_price) - 安全:使用
?佔位符和元組參數
percent_formatting(嚴重,常見):
- 不安全:
"... WHERE customer_id = '%s' AND status = '%s'" % (customer_id, status) - 安全:
execute("... WHERE customer_id = %s AND status = %s", (customer_id, status))
string_concatenation(嚴重,常見):
- 不安全:
"DELETE FROM " + table + " WHERE id = " + record_id - 安全:對資料表名稱使用允許清單驗證,對 ID 使用參數化
orm_raw_query_misuse(高,中等):
- 不安全:在 Django 的
connection.cursor()中使用 f 字串 - 安全:使用位置參數(
%s)和列表
進階 LLM SQL 注入模式
動態資料表和欄位名稱
LLM 生成的最棘手模式涉及動態資料表或欄位名稱。參數化查詢無法處理識別符(資料表名稱、欄位名稱),因此 LLM 退回到字串格式化。不安全版本:
# LLM 常見生成(不安全)
def get_column_value(table: str, column: str, record_id: int):
query = f"SELECT {column} FROM {table} WHERE id = {record_id}"
return db.execute(query).fetchone()安全版本使用允許清單:定義 ALLOWED_TABLES 和 ALLOWED_COLUMNS(字典),在執行查詢前驗證 table 和 column 是否在允許集合中,然後只在識別符驗證後才使用 f 字串。
ORM 繞過模式
LLM 有時生成透過使用原始 SQL 繞過 ORM 保護的程式碼:
- 不安全:
session.execute(text(f"SELECT * FROM users WHERE name LIKE '%{search_term}%'"))繞過 SQLAlchemy 的參數化 - 安全:
session.execute(text("SELECT * FROM users WHERE name LIKE :pattern"), {"pattern": f"%{search_term}%"}) - 最佳:使用 ORM 方法(
select(User).where(User.name.contains(search_term)))
LIKE 子句注入
LLM 經常錯誤處理 LIKE 子句:
- 不安全(基本):f 字串 LIKE 查詢
- 部分修復(仍然不安全):參數化但未逸出 LIKE 萬用字元
- 安全:逸出用戶輸入中的
%和_字元,使用ESCAPE '\\'
使用靜態分析偵測
Semgrep 規則覆蓋四種模式:
python-sql-fstring-injection:偵測$CURSOR.execute(f"...{$VAR}...")python-sql-format-injection:偵測$CURSOR.execute("...".format(...))python-sql-concat-injection:偵測$CURSOR.execute("..." + $VAR + "...")sqlalchemy-raw-fstring:偵測text(f"...{$VAR}...")繞過 ORM 參數化
CodeQL 查詢配置 SqlInjectionConfig:源是 RemoteFlowSource,匯是 SqlExecution.getSql(),在兩者之間追蹤污點流。
CI/CD 整合
自動 SQL 注入偵測管線
Shell 腳本執行兩層檢查:
- 如果安裝了
semgrep,執行 SQL 注入規則並計算發現數量 - 使用 grep 計數 f 字串和
.format()SQL 模式 - 如果發現任何模式則以非零退出碼退出(觸發 CI 失敗)
Python 提交前鉤子
SQLInjectionDetector 是基於 AST 的偵測器,透過 visit_Call 方法檢查 .execute() 呼叫的第一個參數:
isinstance(node, ast.JoinedStr):偵測 f 字串(包含類似 SQL 關鍵字的 SQL 關鍵字)node.func.attr == "format":偵測.format()格式化isinstance(node.op, ast.Add):偵測字串連接(標記為警告,需手動確認)
開發人員教育
快速參考:安全 SQL 模式
| 框架 | 不安全(LLM 常生成) | 安全 |
|---|---|---|
| sqlite3 | execute(f"... {var} ...") | execute("... ? ...", (var,)) |
| psycopg2 | execute(f"... {var} ...") | execute("... %s ...", (var,)) |
| MySQL Connector | execute(f"... {var} ...") | execute("... %s ...", (var,)) |
| SQLAlchemy Core | text(f"... {var} ...") | text("... :param ..."), {"param": var} |
| Django ORM | raw(f"... {var} ...") | raw("... %s ...", [var]) |
AI 輔助開發的提示詞工程
透過在提示詞中明確指定可以降低 SQL 注入風險:
- 不說「寫一個按名稱搜尋用戶的函式」,說「寫一個使用參數化查詢按名稱搜尋用戶的函式」
- 不說「為報告建立資料庫查詢」,說「使用 SQLAlchemy ORM 為報告建立參數化資料庫查詢」
- 在項目
.cursorrules或CLAUDE.md中加入安全規則:永不使用 f 字串、.format()或字串連接執行 SQL 查詢;始終使用帶佔位符的參數化查詢;對動態資料表/欄位名稱始終使用允許清單
參考資料
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command — https://cwe.mitre.org/data/definitions/89.html
- "Do Users Write More Insecure Code with AI Assistants?" — Perry et al., Stanford University, 2023 — https://arxiv.org/abs/2211.03622
- OWASP Top 10 for LLM Applications 2025 — LLM02: Insecure Output Handling — https://genai.owasp.org/llmrisk/
- Semgrep SQL Injection Rules — https://semgrep.dev/p/python-sql-injection
- OWASP SQL Injection Prevention Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
- CodeQL Python SQL Injection Queries — https://codeql.github.com/codeql-query-help/python/py-sql-injection/
為什麼 LLM 生成的程式碼中 LIKE 子句比其他 SQL 查詢更難正確參數化?