快速結論
AI 寫程式快,但快出來的程式碼不一定安全。飛飛在企業內訓課程中,反覆看到同一批漏洞模式從不同的 AI 工具產出的程式碼裡冒出來,而且集中在幾個固定的類型。這篇對照 OWASP Top 10,整理 AI 生成程式碼最常出現的漏洞、實際的錯誤與修正範例、怎麼用工具稽核,以及在台灣處理個資相關程式碼時要注意的合規問題。
為什麼 AI 會寫出有漏洞的程式碼
三個核心原因。
訓練資料裡混雜大量舊的、不安全的程式碼範例。網路上流傳的教學文章、Stack Overflow 的舊回答、開源專案裡的範例程式碼,很多是十年前寫的,當時的最佳實踐(例如用 MD5 做密碼雜湊)現在已經不安全,但這些模式仍大量存在於訓練資料中,AI 學到的是統計上常見的寫法,不是最新的安全標準。
AI 沒有執行環境的即時脈絡。它不知道這段程式碼最終會部署在什麼樣的網路環境、面對什麼樣的攻擊面、資料庫裡存的是不是敏感個資。少了這些脈絡,AI 傾向產出「功能上能動」的最簡版本,而不是「在這個威脅模型下安全」的版本。
AI 的優化目標是讓程式碼能動、能通過你描述的需求,不是預設要通過資安稽核。當你的 Prompt 只寫「幫我寫一個查詢使用者資料的 API」,AI 會給你一個能查到資料的版本,不會主動幫你加上輸入驗證、速率限制、權限檢查,除非你明確要求。
這三個原因合起來的結果是,AI 生成的程式碼在功能面往往一次就能動,但安全面需要額外一輪把關。飛飛在企業內訓中常用的比喻是,AI 像一個手腳很快但沒受過資安教育的新人工程師,交出來的東西能跑,但你不會讓他不經 Review 就直接上線。
OWASP Top 10 對照:AI 常見漏洞模式
以下對照 OWASP Top 10(2021 版)架構,列出實測中 AI 最常犯的錯誤類型。
A01 Broken Access Control(權限控制失效)
AI 生成的 API 端點經常只做了「有沒有登入」的檢查,漏掉「這個使用者是否有權限存取這筆資料」的檢查。
有漏洞的版本(Node.js / Express):
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
const order = await Order.findById(req.params.orderId);
res.json(order);
});
這段程式碼確認了使用者有登入(authenticate middleware),但沒有確認這筆訂單是不是屬於這個使用者。任何登入的使用者只要換一個 orderId 就能看到別人的訂單,這是典型的不安全直接物件參照(IDOR)。
修正版本:
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
const order = await Order.findById(req.params.orderId);
if (!order || order.userId !== req.user.id) {
return res.status(404).json({ error: 'Order not found' });
}
res.json(order);
});
多加一行判斷 order.userId !== req.user.id,補上物件層級的授權檢查。回傳 404 而不是 403,避免洩漏「這筆訂單存在但你沒有權限」這個資訊。
A02 Cryptographic Failures(加密機制失效)
AI 常用已知不安全的雜湊演算法,或是把金鑰、密碼直接寫死在程式碼裡。
有漏洞的版本(Python):
import hashlib
def hash_password(password):
return hashlib.md5(password.encode()).hexdigest()
API_KEY = "sk-live-abc123xyz789"
MD5 早已被證實可以用彩虹表快速破解,不該用來存密碼。API Key 直接寫在原始碼裡,一旦程式碼外流(推到公開 repo、被反編譯)金鑰就直接暴露。
修正版本:
import bcrypt
import os
def hash_password(password: str) -> bytes:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt())
def verify_password(password: str, hashed: bytes) -> bool:
return bcrypt.checkpw(password.encode(), hashed)
API_KEY = os.environ["API_KEY"]
改用 bcrypt(內建加鹽與可調整運算成本),密碼雜湊有專門設計來抵抗暴力破解。API Key 改從環境變數讀取,原始碼裡不出現任何真實金鑰。
A03 Injection(注入攻擊)
字串拼接組 SQL 或 Shell 指令,是 AI 生成程式碼裡最常見也最危險的模式。
有漏洞的版本(Python):
def get_user(username):
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
return cursor.fetchone()
如果 username 是 ' OR '1'='1,組出來的 SQL 會變成永真條件,繞過原本的查詢邏輯,這是教科書等級的 SQL Injection。
修正版本:
def get_user(username):
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))
return cursor.fetchone()
改用參數化查詢(Parameterized Query),資料庫驅動會把 username 當成純資料處理,不會被解讀成 SQL 語法的一部分。
Shell 指令注入的例子(Node.js):
const { exec } = require('child_process');
function convertImage(filename) {
exec(`convert ${filename} output.png`);
}
如果 filename 是 image.jpg; rm -rf /,會直接執行任意指令。
修正版本:
const { execFile } = require('child_process');
function convertImage(filename) {
execFile('convert', [filename, 'output.png']);
}
改用 execFile 並把參數用陣列傳入,不經過 Shell 解析,避免指令注入。
A07 Identification and Authentication Failures(身分驗證失效)
AI 常忽略 Session 過期機制、速率限制,導致暴力破解攻擊變得容易。
有漏洞的版本(Node.js):
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (user && await bcrypt.compare(password, user.passwordHash)) {
const token = jwt.sign({ id: user.id }, SECRET, { expiresIn: '30d' });
res.json({ token });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
沒有速率限制,攻擊者可以無限次嘗試密碼。Token 有效期 30 天也偏長,一旦外洩,攻擊者可以長期使用。
修正版本:
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts, please try again later'
});
app.post('/login', loginLimiter, async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (user && await bcrypt.compare(password, user.passwordHash)) {
const token = jwt.sign({ id: user.id }, SECRET, { expiresIn: '1h' });
res.json({ token });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
加上 express-rate-limit,同一個 IP 在 15 分鐘內最多嘗試 5 次登入。Token 有效期縮短為 1 小時,搭配 Refresh Token 機制平衡安全性和使用體驗。
A08 Software and Data Integrity Failures(軟體與資料完整性失效)
AI 給的安裝指令經常沒有鎖定版本,或建議直接從不明來源安裝套件。
有問題的做法:
npm install express
pip install requests
沒有指定版本,也沒有搭配 lockfile(package-lock.json、requirements.txt 加版本號),每次安裝可能抓到不同版本,如果上游套件被植入惡意程式碼(supply chain attack),專案會在不知情的狀況下引入風險。
比較穩健的做法:
npm install [email protected] --save-exact
npm ci # 用 package-lock.json 做精確安裝,CI 環境建議用這個而非 npm install
pip install requests==2.32.3
pip install -r requirements.txt --require-hashes # 搭配 hash 驗證套件完整性
鎖定確切版本,並在 CI 流程中用 npm ci 或加上 hash 驗證,確保每次安裝的套件內容一致,不會被竄改。
稽核 AI 生成程式碼的方法
光靠人工肉眼看,很容易漏掉前面列的這些模式。實務上會搭配自動化工具。
靜態分析工具
Semgrep。開源的靜態分析工具,支援 Python、JavaScript、Go 等多種語言,內建大量針對 OWASP Top 10 的規則集,可以直接掃描整個專案:
semgrep --config=p/owasp-top-ten ./src
Bandit。專門針對 Python 的安全靜態分析工具,會標記出使用 eval、硬編碼密碼、不安全的隨機數產生器等問題:
bandit -r ./src -f json -o bandit-report.json
這兩個工具的共同重點是可以整合進 CI 流程,讓 AI 生成的程式碼在合併前自動經過一輪規則掃描,抓到人工容易漏看的模式。
依賴掃描
AI 建議安裝的套件,需要額外確認套件本身有沒有已知漏洞。
npm audit
pip-audit
npm audit 會對照已知漏洞資料庫,列出專案依賴中有漏洞的套件版本並建議修復方式。pip-audit 是 Python 生態對應的工具。這兩個指令建議放進 CI,每次建構都跑一次,而不是只在專案初期跑一次就不管。
實務上還會搭配 npm audit fix 或 pip-audit --fix 做自動修復,但自動修復可能把套件升級到有相容性風險的版本,建議先在測試環境跑過完整測試再合併,不要在正式環境直接執行自動修復指令。對於 AI 建議安裝的套件,如果是不常見的名稱,額外花一分鐘查一下套件的下載量、維護紀錄和 GitHub star 數,可以過濾掉不少風險。
手動 Review 檢查清單
自動化工具抓不到的部分,需要人工對照這份清單:
- 每一個接受使用者輸入的地方,輸入是否有經過驗證或參數化處理
- 每一個回傳資料的端點,是否確認了資料歸屬(物件層級授權)
- 密碼、金鑰、Token 是否曾經以明文形式出現在程式碼或設定檔裡
- 錯誤處理是否會把內部細節(Stack Trace、SQL 語句、檔案路徑)回傳給使用者
- 新增的依賴套件,是否來自官方或可信來源,star 數與維護狀態是否正常
- 涉及金流或個資的邏輯,是否有額外的日誌記錄,方便事後稽核
台灣情境:個資法合規
如果 AI 生成的程式碼會處理個人資料(姓名、身分證字號、電話、地址、健康資料等),台灣的個人資料保護法對蒐集、處理、利用有明確規範。這部分除了資安考量,還牽涉法遵責任,工程團隊在設計階段就要把這兩個面向一起考慮進去。
常見的落差是,AI 生成的程式碼預設不會加上:
資料最小化的邏輯。查詢使用者資料時,AI 傾向用 SELECT * 撈出整張表的所有欄位,即使畫面只需要顯示姓名。個資法要求蒐集和利用要符合特定目的、範圍最小化,實務上應該明確指定欄位,只撈需要的資料。
刪除與更正機制。個資法賦予當事人請求刪除、更正個資的權利。AI 生成的 CRUD 程式碼通常只有基本的增刪改查,不會主動考慮「使用者要求徹底刪除所有相關資料,包含備份和記錄檔」這種完整的刪除流程。
存取記錄。誰在什麼時候查詢了誰的個資,這類稽核軌跡(Audit Trail)需要額外設計,AI 不會主動幫你加上,除非在 Prompt 裡明確要求。
跨境傳輸的資料位置。如果程式碼會把個資送到雲端 AI 服務做處理(例如用 LLM API 分析客戶資料),需要確認資料實際儲存和處理的地理位置,是否符合公司對客戶的承諾或行業主管機關的要求(例如金融業對地端部署的要求)。
實務上的做法是把個資法的要求(最小化蒐集、當事人權利、稽核軌跡)寫進需求文件或 Prompt 裡,明確要求 AI 依此設計程式碼,而不是等 AI 生成完再回頭補。
安全與限制
用工具稽核 AI 生成的程式碼,同樣有限制需要理解。
靜態分析工具有其規則覆蓋範圍,新出現的攻擊手法或非典型的漏洞模式,工具規則庫可能還沒收錄,掃描通過不代表完全沒有問題。
工具會有誤判(False Positive),過度依賴掃描結果、忽略人工複核,可能把資源花在修不重要的警告,卻漏掉真正的問題。
依賴掃描只能抓到已知漏洞資料庫裡記錄過的問題,對於零時差漏洞(Zero-day)或供應鏈攻擊的新手法無能為力。
個資法合規判斷需要法務或合規人員的專業意見,工程師用工具掃描出的技術問題,不等於法遵已經到位,涉及個資處理的系統上線前建議有正式的法遵複核流程。
知識檢測
讀完文章後,測試一下你對這個主題的理解。
常見問題
AI 生成的程式碼是不是都有漏洞?
不是每一段都有,但比例明顯高於資深工程師手寫且經過審查的程式碼。實測中,涉及使用者輸入處理、權限判斷、加密相關的程式碼片段最容易出現前面提到的模式。功能單純、沒有外部輸入的程式碼相對安全。
哪一種漏洞在 AI 生成程式碼裡最常見?
以飛飛實測和企業內訓的觀察,權限控制失效(A01)和注入攻擊(A03)出現頻率最高,原因是 AI 傾向先滿足「功能能動」,除非 Prompt 明確要求,否則不會主動加上授權檢查或做輸入驗證。
只靠 Semgrep 或 Bandit 掃描夠嗎?
不夠。這類工具能抓到規則庫涵蓋的已知模式,但商業邏輯層級的授權問題(例如訂單歸屬檢查)、需要理解上下文才能判斷的問題,工具不一定抓得到。建議搭配前面提到的手動檢查清單,工具加人工複核才完整。
要求 AI 在 Prompt 裡就寫出安全的程式碼可行嗎?
可行,而且效果明顯。在 Prompt 裡明確列出安全要求(例如「使用參數化查詢」「密碼用 bcrypt 雜湊」「加上物件層級的權限檢查」),AI 產出的第一版品質會比只寫功能需求好很多。但仍建議產出後再過一次稽核流程,不要完全依賴 Prompt 就假設一定做對。
個資相關的程式碼可以完全交給 AI 生成嗎?
技術實作可以借助 AI 加快速度,但涉及個資蒐集範圍、保存期限、當事人權利機制的設計,建議由熟悉個資法的人員參與需求確認,AI 生成的程式碼只是實作層面的產出,法遵層面的判斷仍需要人來把關。
相關文章
- 用 AI 做 Code Review:GitHub Copilot vs Cursor vs Claude Code 實測
- Prompt Injection 是什麼?
- Data Leakage 是什麼?
- Fine-tuning 是什麼?
參考資料
- OWASP Top 10:2021 — OWASP Top 10 網頁應用安全風險官方文件
- Semgrep Documentation — Semgrep 靜態分析工具文件
- Bandit Documentation — Bandit Python 安全掃描工具文件
- npm audit Documentation — npm audit 官方文件
- pip-audit — PyPA — pip-audit 官方套件頁面
- 個人資料保護法 — 台灣個人資料保護法全文