API 防重放小記(replay attack)
最近在想 API 傳輸安全時,順手重新整理了一次 replay attack。這個主題很常被一句「做簽章就好」帶過,但實際上如果只做簽章、不做時效與唯一性控制,重放攻擊還是擋不住。
什麼是 replay attack
Replay attack 中文常翻成重放攻擊。意思是攻擊者不一定要看懂內容,也不一定要竄改內容,只要把一個原本合法的請求原封不動重送一次,就可能造成問題。
例如:
- 扣款 API 被重送一次,重複扣款。
- 領券 API 被重送多次,重複領取。
- 開門、解鎖、下單這類操作被重播。
也就是說,資料內容沒被改,系統還是可能中招。
只做簽章為什麼不夠
很多人第一反應是做 sign 簽章。簽章確實能防止內容被竄改,但如果攻擊者拿到一包「原本就合法」的請求,連同 body、timestamp 以外的所有內容一起重送,server 還是可能驗證通過。
所以防重放至少要同時處理三件事:
- 請求內容沒有被改。
- 請求有時效。
- 請求只能被接受一次。
常見做法
1. 簽章
客戶端把重要欄位組成固定字串,再用共享密鑰或私鑰做簽章。Server 收到後用同樣規則驗證,確認內容沒被改。
2. Timestamp
請求帶上時間戳記,例如只接受 5 分鐘內的請求。這樣就算封包被攔到,也不能無限期重送。
3. Nonce
每次請求額外帶一個隨機唯一值。Server 驗證成功後,把這個 nonce 記錄下來,短時間內如果又收到一模一樣的 nonce,就拒絕。
這一步通常才是真正防 replay 的關鍵。
4. Token / 身分驗證
如果不是完全開放的 API,搭配 token 或登入機制可以再把攻擊面縮小。至少不是任何人攔到封包就能隨便打。
一個簡化流程
我自己比較容易記的流程是:
- 客戶端準備
path + body + timestamp + nonce。 - 用共享密鑰算出簽章。
- Server 驗證簽章是否正確。
- Server 檢查 timestamp 是否過期。
- Server 檢查 nonce 是否已經用過。
- 全部通過才真的執行業務邏輯。
只少其中一層,效果都會差很多。
我當時想到的疑問
我那時也有想到一個很實際的問題:如果 App 被逆向,密鑰被挖出來,不就還是無解?
這個問題其實沒錯。所以這類防護很多時候是在防:
- 中間人攔包重送。
- 一般腳本重放。
- 低成本濫用。
它不是在保證「App 被完整破解後仍百分之百安全」。那又是另一個層級的攻防問題。
我的結論
防重放不是單一功能,而是一組策略。只做 sign 比只上 HTTPS 好,但還不夠完整。比較實際的組合通常是:HTTPS + 簽章 + timestamp + nonce + 身分驗證,再搭配 server 端真正的冪等設計。