競態條件(Race Condition)發生在兩個流程同時根據某個初始條件競爭執行,但條件在過程中已被另一方改變——兩者卻都依舊完成。
經典銀行轉帳例子#
- 你帳戶有 $500,想全數轉給朋友
- 用手機 App 送出 $500 的轉帳請求
- 過 10 秒還在處理,你不耐煩,改用筆電登入——看到餘額仍是 $500,再次送出 $500 轉帳
- 兩個請求在幾秒內陸續完成
- 你的帳戶 $0,朋友卻說收到 $1,000
真實銀行有嚴格交易鎖定不會這樣,但這個故事說明核心:「檢查條件」與「執行動作」之間有時間差,期間若條件被改變,兩個流程都會以舊條件走完。
Web 應用中的成因#
HTTP 請求看似瞬間完成,但伺服器需要:
- 重新驗證身份
- 載入資料、跑業務邏輯
- 寫回資料庫
任何「先查再寫」的流程,在「查詢」與「寫入」之間都可能被另一個請求插入。
案例 1:Accepting a HackerOne Invite Multiple Times#
- 難度:低
- URL:
hackerone.com/invitations/<INVITE_TOKEN>/- 回報來源:https://hackerone.com/reports/119354/ ↗
- 回報日期:2016-02-28
- 獎金:紀念品(Swag)
漏洞描述#
HackerOne 邀請函的設計:每個邀請是一條唯一 token URL,理論上只能被使用一次。流程大概是:
- 在資料庫查找 token
- 套用業務邏輯,把帳號加入專案
- 標記 token 已使用
本書作者用三個帳號(A、B、C)測試:A 建立專案邀請 B,他在兩個瀏覽器分別以 B 和 C 登入並打開同一封邀請;把兩個瀏覽器的「Accept」按鈕排在螢幕同位置,幾乎同時按下。

Figure 15-1: 兩個堆疊的瀏覽器視窗顯示同一份 HackerOne 邀請
第一次失敗、第二次成功——兩個帳號都用同一封邀請加入專案。
Takeaways#
簡單的競態條件可以靠手動同步點擊測試(按鈕對齊、極速雙擊)。複雜流程則需自動化工具——詳見下一個案例。
案例 2:Exceeding Keybase Invitation Limits#
- 難度:低
- URL:
https://keybase.io/_/api/1.0/send_invitations.json/- 回報來源:https://hackerone.com/reports/115007/ ↗
- 回報日期:2015-02-05
- 獎金:$350
漏洞描述#
Keybase 限制每個註冊使用者只能送 3 張邀請。研究者 Josip Franjković 推測流程:
- 收到邀請請求
- 檢查資料庫該使用者剩餘邀請數
- 產生 token、寄信
- 扣減邀請數
他用 Burp Intruder 同時送出多個邀請請求,繞過 3 張限制送出 7 張邀請。Keybase 後來用 lock(鎖機制)限制資源並發存取。
Takeaways#
Burp Intruder 可以指定 payload 列表(多個 Email)並在同一時間送出多個請求。任何「有上限」的功能(試用配額、註冊次數、優惠券領取)都值得試。
案例 3:HackerOne Payments Race Condition#
- 難度:低
- 回報日期:2017-04-12
- 獎金:$1,000
漏洞描述#
HackerOne 把同一天的多筆獎金合併成一次 PayPal 付款。流程使用背景工作(background job):
- 收 HTTP 請求 → 把支付任務丟進佇列 → 背景批次跑
研究者 Jigar Thakkar 發現:若兩位 HackerOne 使用者註冊同一個 PayPal Email,HackerOne 會合併獎金、丟一筆給該 Email。但若有人在「合併好」與「背景送出」之間改成另一個 PayPal Email,背景工作會把這筆款項送到新地址——而原 Email 也仍會收到(依照當時實作觀察)。
「Time of Check vs Time of Use(TOCTOU)」——條件檢查與條件使用之間的時差,是背景工作型競態的核心。
Takeaways#
看到網站「動作完成後過一陣子才執行」就是背景工作的訊號。試著在「丟進佇列」與「執行」之間改變條件,看新條件是否會被使用。
案例 4:Shopify Partners Race Condition#
- 難度:高
- 回報來源:https://hackerone.com/reports/300305/ ↗
- 回報日期:2017-12-24
- 獎金:$15,250
背景#
Shopify Partners 平台讓開發者向商店申請協作存取。Email 驗證流程:
- Shopify 寄一封驗證信
- 點擊內含 URL 後,伺服器把 Email 標記為已驗證
研究者 Tanner Emek 從 @uzsunny 的舊報告中得知:當兩個 partner 帳號用同一 Email 對同一商店申請存取時,Shopify 程式會自動把店家既有的員工帳號轉為協作者帳號——可能不需店家確認。
漏洞描述#
Emek 結合競態條件,把 Email 換成他不擁有的 cache@hackerone.com:
- 用自己的 Email 註冊 partner,收到驗證信但不立刻點
- 在 Partners 平台改 Email 為
cache@hackerone.com,用 Burp 攔截改 Email 的請求 - 點驗證連結,用 Burp 攔截驗證請求
- 用 Burp 幾乎同時送出兩個請求
伺服器處理時,把驗證流程套用到新 Email 上——成功讓 cache@hackerone.com 在 Shopify 系統中被驗證為他擁有。再用 @uzsunny 的轉換漏洞,無需店家同意即可存取任何該 Email 對應的 Shopify 商店。
Shopify 修復方式:對帳號紀錄加 lock;強制協作者請求須由店家同意。
Takeaways#
- 修復過的漏洞要回頭再試——同一塊功能可能藏著新的入口
- 任何「驗證系統」都是測試重點,特別是「Email/手機號變更 + 驗證 token」的組合
章末總結#
- 競態條件出現在「先查條件 → 再做動作」的流程,且查與做之間有時間差
- 高 CP 值的測試場景:
- 一次性使用的 token(邀請、優惠券、密碼重設)
- 有配額/上限的功能
- 背景工作(付款、Email、訊息推播)
- 帳號驗證、Email 變更
- 工具:Burp Intruder 一次送出多個請求、自寫腳本控制毫秒級併發
- 修復後的功能仍要回頭測——新的程式碼可能是新的攻擊面