National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 78 24°C PM2.5 18
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 07

SQL 注入

字串組合查詢的風險與參數化查詢
Interaction 40 minute(s)

Learning Objectives

  • 說明字串組合查詢為什麼會讓輸入改變查詢的結構
  • 在模擬網站上觀察登入繞過與跨資料表讀取
  • 以參數化查詢修補漏洞,並確認攻擊失效
  • 處理無法參數化的欄位名稱,並說明資料庫帳號最小權限的作用

資料與指令的混合

網站處理登入時,常需要依使用者輸入查詢資料庫。下列程式以字串相加的方式組成查詢:

sql = "SELECT * FROM users WHERE email = '" + email + "' AND password = '" + password + "'"

開發者預期 email 只是一段資料,但資料庫收到的是整段文字,無從分辨哪些字來自程式、哪些字來自使用者。若輸入中含有單引號,字串會提早結束,其後的文字便成為 SQL 指令的一部分。這類漏洞稱為 SQL 注入(SQL injection),根源在於資料與指令混在同一段文字。

以信箱欄位輸入 ' OR 1=1 -- 為例,組合後的查詢如下:

SELECT * FROM users WHERE email = '' OR 1=1 --' AND password = '...'

-- 之後是註解,密碼條件因此失效;1=1 恆為真,查詢會傳回資料表的所有列。程式取第一列,也就是管理員帳號。

本課的模擬環境。 下方網站是真的 Flask 與 SQLite,在瀏覽器內執行,不連線到任何外部系統,帳號與資料皆為虛構。在未獲授權的情況下對真實網站進行相同操作可能觸犯法律(第 1 課)。

互動練習:登入繞過與修補

本區塊為模擬環境。 網站是真的 Flask 與 SQLite,只在你的瀏覽器內執行,不連線到任何外部系統,帳號與資料皆為虛構。未經授權對真實網站進行相同操作可能觸犯法律(第 1 課)。

按「啟動伺服器」後依任務操作。程式可以直接修改,修改後再按一次啟動。「自動檢查」頁面由唯讀的 lab_audit.py 提供,它會對網站送出多種請求,並列出每一項的結果。

參數化查詢的作用

參數化查詢將 SQL 結構與資料分開交給資料庫:

row = db.execute('SELECT * FROM users WHERE email = ? AND password = ?', (email, password)).fetchone()

資料庫先解析帶有佔位符的查詢,結構在此時已經固定,之後傳入的值只會被當成資料。' OR 1=1 -- 不再是語法,而是一個內容奇特的信箱字串,比對結果為查無此人。

部分驅動程式(例如 PyMySQL)是由驅動程式依資料庫的規則轉義後再組成查詢,結構同樣不會被輸入改變。重點是把值交給 API 的參數,不要自己組字串或自己轉義。

常見的不完整作法如下:

作法 問題
移除或跳脫單引號 無法涵蓋所有情境,例如數字欄位不需要引號就能注入
只在前端驗證 攻擊者可以直接送出請求,略過瀏覽器的檢查
以黑名單過濾關鍵字 大小寫、註解與編碼變化難以窮舉,也可能擋下合法輸入
使用 ORM 就不必注意 ORM 通常會參數化,但手寫的原始查詢與字串組合仍有風險

輸入驗證仍然有價值,可以減少異常資料,但不能取代參數化查詢。

UNION 與跨資料表讀取

注入的影響不限於繞過登入。UNION 可以把另一個查詢的結果接在原查詢之後,因此若頁面會顯示查詢結果,攻擊者就可能讀取其他資料表。使用 UNION 時,兩個查詢的欄位數必須相同。

下方的課程查詢有相同的問題,資料庫裡另有一張不應公開的資料表 staff_notes。頁面底部會顯示實際執行的 SQL,方便觀察輸入如何改變查詢。真實網站不應顯示這些資訊。

在 SQLite 中,sqlite_master 記錄了所有資料表的名稱與定義,存在注入漏洞時也能用相同方式讀取,所以資料表名稱保密不能當作防護,仍要修補漏洞本身。另外,錯誤訊息會透露查詢的結構,也不應直接顯示給使用者。

互動練習:跨資料表讀取與欄位名稱

本區塊為模擬環境。 網站是真的 Flask 與 SQLite,只在你的瀏覽器內執行,不連線到任何外部系統,帳號與資料皆為虛構。未經授權對真實網站進行相同操作可能觸犯法律(第 1 課)。

查詢欄輸入的內容會放進 LIKE '%…%' 裡。排序欄位 sort 位於網址,不在表單的選項限制之內。

欄位名稱與縱深防禦

佔位符只能代表值,不能代表資料表或欄位名稱,所以 ORDER BY ? 達不到排序的效果。這類位置應該以允許清單對應:程式只接受事先列出的選項,其他輸入一律採用預設值或拒絕。

修補漏洞之外,還應假設防線可能失效,這種作法稱為縱深防禦:

  • 資料庫帳號最小權限:應用程式的帳號只取得必要權限。注入發生時,影響範圍受到限制,例如不能刪除資料表
  • 不顯示詳細錯誤:錯誤內容寫入日誌,頁面只顯示通用訊息(第 12、16 課)
  • 敏感資料另行保護:密碼只存慢速雜湊值(第 2 課)。資料外洩時,攻擊者無法直接取得明文,猜測的成本也大幅提高,但弱密碼仍可能被猜中
  • 監控異常查詢:大量錯誤與異常的查詢長度可能是攻擊的跡象,需要結合其他紀錄判斷(第 16 課)

重點整理

  • SQL 注入的根源是資料與指令混在同一段文字,資料庫無法分辨兩者
  • 參數化查詢讓查詢結構先固定,輸入只會被當成資料;這是主要的防護方式
  • 過濾引號、前端驗證與黑名單都不足以取代參數化查詢
  • 佔位符不能代表欄位名稱,這類位置要使用允許清單
  • 資料庫帳號最小權限、不顯示詳細錯誤與監控,可以在防線失效時降低影響
  • 只在自己擁有或獲得明確授權的系統上進行測試

下一課討論另一種資料與指令混合的情形:跨站腳本。

小測驗

1.

參數化查詢能防止 SQL 注入,主要原因是什麼?

2.

網站在前端以 JavaScript 擋下單引號,後端仍以字串組合查詢。這樣是否安全?

3.

下列何者無法使用佔位符,應改用允許清單?

4.

應用程式的資料庫帳號只有部分資料表的讀寫權限。這項設定的作用是什麼?

5.

使用 UNION 讀取其他資料表時,有什麼限制?

第 1 次作答
這一課完成了!
Next lesson:跨站腳本(XSS)
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。