不安全設計概述
不安全設計(Insecure Design)是 OWASP Top 10 2021 年版本中新增的類別,位列第四名。這個類別聚焦於與設計和架構缺陷相關的風險,強調在開發之前就需要進行安全設計和威脅建模的重要性。
不安全設計指的是在系統設計階段就存在的根本性安全缺陷,這些缺陷無法透過完美的實作來修復。即使程式碼撰寫得毫無瑕疵,如果設計本身就有問題,系統仍然會存在安全風險。
不安全設計的特徵:
- 缺乏安全需求的定義
- 未進行威脅建模分析
- 忽略安全設計原則
- 缺少業務邏輯驗證
- 未考慮濫用場景(Abuse Cases)
設計缺陷 vs 實作缺陷
理解設計缺陷和實作缺陷的區別對於建立安全系統至關重要。
設計缺陷(Design Flaws)
設計缺陷是在系統架構和設計階段產生的問題,通常需要重新設計才能修復。
範例:
1
2
3
4
5
6
7
| 問題:密碼重設功能透過安全問題驗證身份
設計:使用者可以設定「您的寵物名字是什麼?」作為安全問題
缺陷分析:
- 安全問題的答案通常可以從社群媒體找到
- 答案的變化有限,容易被暴力破解
- 這是設計層面的問題,無法透過更好的實作來修復
|
實作缺陷(Implementation Bugs)
實作缺陷是在程式碼層面產生的問題,可以透過修改程式碼來修復。
範例:
1
2
3
4
5
6
| # 實作缺陷:SQL 注入
query = "SELECT * FROM users WHERE id = " + user_input
# 修復後的實作
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_input,))
|
兩者的關係
| 類型 | 發生階段 | 修復方式 | 成本 |
|---|
| 設計缺陷 | 架構設計 | 重新設計 | 高 |
| 實作缺陷 | 程式開發 | 修改程式碼 | 中低 |
常見不安全設計模式
1. 缺乏速率限制
許多系統未對敏感操作實施速率限制,導致暴力破解攻擊變得可行。
不安全設計:
1
2
3
4
| 登入功能:
- 無登入嘗試次數限制
- 無帳號鎖定機制
- 無驗證碼機制
|
安全設計:
1
2
3
4
| 登入功能:
- 5 次失敗後啟用驗證碼
- 10 次失敗後暫時鎖定帳號 15 分鐘
- 記錄異常登入行為並發送警報
|
2. 不安全的密碼重設流程
不安全設計範例:
1
2
3
4
5
| 步驟 1:輸入電子郵件
步驟 2:回答安全問題
步驟 3:設定新密碼
問題:安全問題答案容易被猜測或透過社交工程取得
|
安全設計範例:
1
2
3
4
| 步驟 1:輸入電子郵件
步驟 2:發送一次性驗證連結到電子郵件
步驟 3:連結有效期 15 分鐘,使用後立即失效
步驟 4:設定新密碼(需符合密碼政策)
|
3. 信任用戶端輸入
不安全設計:
1
2
3
| // 前端計算價格
const totalPrice = quantity * unitPrice * (1 - discount);
// 直接將計算結果發送到後端
|
安全設計:
1
2
3
4
5
6
| # 後端驗證所有計算
def calculate_order_total(order_id, quantity):
product = get_product(order_id)
discount = get_user_discount(current_user)
# 所有價格計算都在後端完成
return quantity * product.price * (1 - discount)
|
4. 缺乏業務流程驗證
不安全設計:
1
2
3
4
5
| 購物流程:
1. 加入購物車 → 2. 結帳 → 3. 付款
問題:系統未驗證用戶是否已完成前一步驟
攻擊:直接跳到步驟 3 可能繞過某些檢查
|
安全設計:
1
2
3
4
5
| 購物流程(含狀態驗證):
1. 加入購物車 [狀態: CART_READY]
2. 結帳 [需要狀態: CART_READY → CHECKOUT]
3. 付款 [需要狀態: CHECKOUT → PAYMENT]
4. 完成 [需要狀態: PAYMENT → COMPLETED]
|
5. 過度依賴隱藏欄位
不安全設計:
1
2
3
4
5
| <form action="/update-profile">
<input type="hidden" name="user_id" value="12345">
<input type="hidden" name="role" value="user">
<input type="text" name="email" value="user@example.com">
</form>
|
攻擊者可以修改隱藏欄位的值來提升權限或存取其他用戶資料。
威脅建模
威脅建模是在設計階段識別和評估潛在威脅的系統性方法。
STRIDE 威脅模型
STRIDE 是微軟開發的威脅分類框架:
| 威脅類型 | 說明 | 對應安全屬性 |
|---|
| Spoofing | 身份偽造 | 身份驗證 |
| Tampering | 資料竄改 | 完整性 |
| Repudiation | 否認行為 | 不可否認性 |
| Information Disclosure | 資訊洩漏 | 機密性 |
| Denial of Service | 阻斷服務 | 可用性 |
| Elevation of Privilege | 權限提升 | 授權 |
威脅建模步驟
步驟 1:定義系統範圍
1
2
3
| 系統:電子商務平台
元件:Web 前端、API 伺服器、資料庫、付款閘道
資料流:用戶資料、訂單資訊、付款資料
|
步驟 2:建立資料流程圖(DFD)
1
2
3
4
5
| [用戶] ---(HTTPS)---> [Web 應用程式]
|
[API 伺服器]
/ \
[資料庫] [付款閘道]
|
步驟 3:識別威脅
針對每個元件和資料流應用 STRIDE:
1
2
3
4
| 資料流:用戶 → Web 應用程式
- Spoofing:攻擊者偽造用戶身份
- Tampering:中間人攻擊修改請求
- Information Disclosure:資料傳輸未加密
|
步驟 4:評估風險
使用風險評估矩陣:
1
2
3
4
| 風險 = 可能性 × 影響
高風險:需要立即處理
中風險:應在下一版本修復
低風險:可接受或長期計畫修復
|
安全設計原則
1. 最小權限原則(Principle of Least Privilege)
系統元件只應擁有完成其功能所需的最小權限。
1
2
3
4
5
6
7
8
| # Kubernetes Pod 安全設定範例
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
|
2. 縱深防禦(Defense in Depth)
多層安全控制,即使一層被突破,其他層仍能提供保護。
1
2
3
4
5
| 層次架構:
1. 網路層:防火牆、WAF
2. 應用層:輸入驗證、輸出編碼
3. 資料層:加密、存取控制
4. 監控層:日誌記錄、異常偵測
|
3. 預設安全(Secure by Default)
系統預設配置應該是最安全的狀態。
1
2
3
4
5
6
7
| # 不安全:預設允許所有
DEBUG = True
ALLOWED_HOSTS = ['*']
# 安全:預設限制
DEBUG = False
ALLOWED_HOSTS = ['www.example.com']
|
4. 失敗安全(Fail Secure)
當系統發生錯誤時,應該預設拒絕存取而非開放存取。
1
2
3
4
5
6
7
| def check_permission(user, resource):
try:
return permission_service.check(user, resource)
except Exception as e:
logging.error(f"Permission check failed: {e}")
# 失敗時預設拒絕存取
return False
|
5. 信任邊界(Trust Boundaries)
明確定義系統中的信任邊界,跨越邊界的資料必須經過驗證。
1
2
3
| [不信任區域] [信任邊界] [信任區域]
用戶輸入 ---> 驗證/清理 ---> 內部處理
外部 API ---> 驗證/清理 ---> 業務邏輯
|
案例分析
案例 1:電影票預訂系統漏洞
情境:
某電影院的線上訂票系統允許用戶預訂特定場次的座位。
不安全設計:
1
2
3
4
5
6
7
| 流程設計:
1. 用戶選擇場次和座位
2. 系統保留座位 10 分鐘
3. 用戶完成付款
4. 座位確認
問題:系統未限制單一用戶可以預訂的座位數量
|
攻擊場景:
攻擊者可以自動化預訂所有座位,造成其他用戶無法購票,然後在時間到期前取消預訂,反覆進行造成服務中斷。
安全設計修正:
1
2
3
4
5
| 改進措施:
1. 限制每位用戶每個場次最多預訂 6 個座位
2. 同一 IP 每分鐘最多 10 次預訂請求
3. 預訂時需要完成驗證碼
4. 多次取消預訂將觸發帳號審查
|
案例 2:銀行轉帳安全問題
情境:
某銀行的網路銀行允許用戶進行轉帳操作。
不安全設計:
1
2
3
4
5
6
| # 轉帳 API
def transfer(from_account, to_account, amount):
if get_balance(from_account) >= amount:
debit(from_account, amount)
credit(to_account, amount)
return {"status": "success"}
|
問題分析:
- 未驗證用戶是否擁有 from_account
- 可能存在競態條件(Race Condition)
- 缺乏交易記錄和審計日誌
安全設計修正:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| @transaction.atomic
def transfer(user, to_account, amount):
# 驗證用戶權限
from_account = user.get_primary_account()
# 使用資料庫鎖防止競態條件
account = Account.objects.select_for_update().get(id=from_account.id)
if account.balance < amount:
raise InsufficientFundsError()
# 大額轉帳需要額外驗證
if amount > 50000:
require_two_factor_auth(user)
# 執行轉帳並記錄
TransactionLog.create(
from_account=from_account,
to_account=to_account,
amount=amount,
user=user,
ip_address=get_client_ip()
)
account.balance -= amount
account.save()
credit(to_account, amount)
|
安全需求整合
安全需求收集
在專案初期就應該收集安全需求,與功能需求同等重要。
安全需求範本:
1
2
3
4
5
6
7
8
| 需求編號:SEC-001
類別:身份驗證
描述:系統必須支援多因素驗證(MFA)
優先級:高
驗收標準:
- 支援 TOTP 驗證器應用程式
- 支援 SMS 驗證碼
- 高風險操作強制要求 MFA
|
安全需求類別
| 類別 | 範例需求 |
|---|
| 身份驗證 | 密碼複雜度、MFA、Session 管理 |
| 授權 | 角色權限、存取控制清單 |
| 資料保護 | 加密傳輸、靜態加密、資料遮罩 |
| 日誌審計 | 操作記錄、異常偵測、合規報告 |
| 可用性 | 速率限制、防 DDoS、故障轉移 |
將安全納入 SDLC
1
2
3
4
5
6
| 需求階段 → 安全需求分析、濫用案例定義
設計階段 → 威脅建模、安全架構審查
開發階段 → 安全程式碼審查、SAST
測試階段 → 滲透測試、DAST、安全驗收測試
部署階段 → 安全配置審查、弱點掃描
維運階段 → 持續監控、事件回應、更新管理
|
測試方法
設計審查
安全架構審查清單:
濫用案例測試
針對每個功能設計濫用案例:
1
2
3
4
5
6
7
8
| 功能:用戶註冊
正常案例:用戶填寫表單,收到驗證信,完成註冊
濫用案例:
1. 攻擊者批量註冊帳號
2. 攻擊者使用他人電子郵件註冊
3. 攻擊者繞過電子郵件驗證
4. 攻擊者利用註冊功能發送垃圾郵件
|
業務邏輯測試
1
2
3
4
5
6
7
| 測試項目:訂單流程
測試案例:
1. 跳過付款步驟直接完成訂單
2. 修改訂單金額為負數
3. 使用已使用的優惠券
4. 在庫存為 0 時下單
5. 訂單完成後修改訂單內容
|
自動化安全測試
1
2
3
4
5
| # 使用 OWASP ZAP 進行自動化掃描
zap-cli quick-scan --self-contained --start-options '-config api.disablekey=true' http://target.com
# 使用 Nuclei 進行漏洞掃描
nuclei -u http://target.com -t cves/ -t vulnerabilities/
|
防護措施
開發階段
- 安全培訓:確保開發團隊了解常見安全風險
- 威脅建模:在設計階段識別潛在威脅
- 安全需求:將安全需求納入開發計畫
- 程式碼審查:進行安全導向的程式碼審查
設計階段
- 採用安全設計模式:使用經過驗證的安全架構
- 實施防禦性設計:假設所有輸入都不可信任
- 限制功能暴露:只暴露必要的功能和資料
- 設計監控機制:內建安全監控和警報功能
實作建議
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| # 業務邏輯驗證範例
class OrderService:
def create_order(self, user, items, discount_code=None):
# 驗證用戶狀態
if not user.is_active:
raise UserInactiveError()
# 驗證商品庫存
for item in items:
if not self.check_inventory(item):
raise OutOfStockError(item)
# 驗證折扣碼
discount = 0
if discount_code:
discount = self.validate_discount(discount_code, user)
# 在後端計算總價
total = self.calculate_total(items, discount)
# 建立訂單並記錄
order = Order.create(
user=user,
items=items,
total=total,
created_at=datetime.now()
)
AuditLog.log(
action='ORDER_CREATED',
user=user,
details={'order_id': order.id, 'total': total}
)
return order
|
參考資料