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)
  • 權限提升(繞過登入)
  • 伺服器控制(讀取系統檔案)

基本攻擊手法:

  1. 認證繞過admin' OR '1'='1admin' --
  2. 資料洩露' OR 1=1' UNION SELECT ...
  3. 資料修改'; UPDATE users SET ...
  4. 資料刪除'; 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

0%