Webhook 安全¶
Webhook 是主要的攻擊面——它們從公開網際網路接收請求,沒有瀏覽器上下文、沒有 Cookie、也沒有 CSRF Token。任何 HTTP 客戶端都可以向 Webhook URL 發送 POST 請求。4pass 實作了四層強制驗證,每一層都必須通過才能執行訂單。任何一層的失敗都會立即終止請求。
4pass 與其他平台的比較
多數基於 Webhook 的交易平台僅依賴單一認證層(URL 令牌或 API 金鑰)。4pass 要求全部四層都通過:(1) 每帳戶唯一 URL 令牌、(2) 可配置時間窗口的時間戳記驗證、(3) 常數時間比較的 Webhook 密鑰、以及 (4) Redis 支援的 SHA-256 重送偵測。每層返回不同的 HTTP 狀態碼,實現精確的攻擊分類。
驗證流程¶
每一層都有獨特的安全用途,並回傳不同的 HTTP 狀態碼,使日誌和監控中的攻擊分類更加精確。
第一層:每帳戶唯一 Token¶
Webhook URL 本身包含一個不透明的 Token,用於識別使用者和特定的交易帳戶:
| 屬性 | 詳情 |
|---|---|
| Token 格式 | 32 位元組 URL 安全字串 (secrets.token_urlsafe(32)) |
| 熵值 | 256 位元 |
| 範圍 | 同時識別使用者和特定交易帳戶 |
| 查詢方式 | Token → TradingAccount → User(單一 DB 查詢加 JOIN) |
| 無效 Token | 404 Not Found —— 不揭露帳戶是否存在 |
| 已停用使用者 | 403 Forbidden —— 帳戶存在但使用者已停用 |
為什麼回傳 404 而非 401?
對無效 Token 回傳 404 Not Found 可防止帳戶列舉攻擊 (Account Enumeration)。攻擊者無法區分「此 Token 不存在」和「此 URL 路徑不存在」。回傳 401 會確認端點存在,並引發進一步的探測。
第二層:時間戳記驗證¶
每個真實交易的 Webhook 請求都必須包含時間戳記。伺服器驗證請求是近期產生的,防止舊的或被擷取的請求在數小時或數天後被重放。
| 屬性 | 詳情 |
|---|---|
| 酬載欄位 | "timestamp": "{{timenow}}" (TradingView 內建變數) |
| 格式 | UTC 時間戳記字串,伺服器端解析 |
| 驗證方式 | |server_time - request_timestamp| < max_request_age |
| 預設最大時效 | 300 秒(5 分鐘) |
| 可設定 | 每帳戶 max_request_age 設定 |
| 失敗 | 401 Unauthorized + webhook_timestamp_expired 稽核事件 + 電子郵件警報 |
| 模擬模式 | 旁通——模擬訂單不需要時間戳記 |
request_age = abs(time.time() - timestamp.timestamp())
if request_age > ctx.max_request_age:
raise HTTPException(status_code=401, detail="Request expired")
時鐘偏差 (Clock Skew)
300 秒的視窗考慮了網路延遲和 TradingView 伺服器與 4pass 之間的輕微時鐘差異。abs() 也處理了 TradingView 時鐘稍微超前的情況。持續遇到時間戳記失敗的使用者可以在帳戶設定中增加 max_request_age。
第三層:密鑰驗證¶
每個交易帳戶都有一個唯一的 Webhook 密鑰,必須包含在請求主體中。這用於驗證發送者身份——即使攻擊者發現了 Webhook URL(第一層),沒有密鑰也無法執行訂單。
| 屬性 | 詳情 |
|---|---|
| 酬載欄位 | "secret": "your-webhook-secret-here" |
| 密鑰格式 | 32 位元組 URL 安全字串 (secrets.token_urlsafe(32)) |
| 熵值 | 256 位元 |
| 比對方式 | hmac.compare_digest(secret, ctx.webhook_secret) —— 常數時間 |
| 失敗 | 401 Unauthorized + webhook_credential_attack 稽核事件 + 電子郵件警報 |
| 輪替 | 使用者可隨時從控制面板重新產生 Webhook 密鑰 |
為什麼使用常數時間比對?
簡單的 secret == expected 比對會透過計時洩漏資訊。如果第一個字元匹配,比對會比不匹配時多花一點時間。攻擊者可以透過測量回應時間逐個字元暴力破解密鑰。hmac.compare_digest() 無論差異在哪裡都以常數時間比對所有位元組。
第四層:重放偵測 (Replay Detection)¶
即使擁有有效的 Token、時間戳記和密鑰,擷取到合法請求的攻擊者仍可在時間戳記有效視窗內重放該請求。第四層透過在 Redis 中追蹤請求指紋來防止此攻擊。
| 屬性 | 詳情 |
|---|---|
| 雜湊輸入 | user_id:action:symbol:quantity:timestamp:secret |
| 演算法 | SHA-256,截取為 32 個十六進位字元 |
| 儲存方式 | Redis 鍵值:webhook:replay:{hash} |
| TTL | max_request_age 秒(與時間戳記視窗相同) |
| 重複檢查 | redis.exists(key) —— 如果鍵值存在,則為重放請求 |
| 失敗 | 409 Conflict + webhook_replay_attack 稽核事件 + 電子郵件警報 |
雜湊中包含 secret 欄位,以防止攻擊者構造具有相同雜湊值的不同請求。TTL 自動使舊雜湊過期,保持 Redis 記憶體使用量在可控範圍內。
額外保護¶
除了四層驗證之外,Webhook 請求在執行訂單前還會經過額外檢查:
| 保護措施 | 實作方式 | 失敗回應 |
|---|---|---|
| 每位使用者速率限制 | 每分鐘 60 次請求,透過 Redis 有序集合滑動視窗 | 429 Too Many Requests |
| IP 白名單 | 可選的每位使用者限制(user_allowed_ips 資料表) |
403 Forbidden + webhook_ip_blocked 警報 |
| 訂閱檢查 | 免費方案使用者限制為模擬交易 | 403 Forbidden |
| 每日訂單上限 | 依方案設定的每日最大訂單數(防止失控交易) | 429 Too Many Requests |
| 憑證驗證 | 若券商憑證尚未驗證則拒絕 | 400 Bad Request |
| 服務條款 | 若使用者未接受最新服務條款則拒絕 | 403 Forbidden |
| 稽核日誌 | 所有嘗試(成功和失敗)均記錄完整的 IP、User-Agent 和上下文 | — |
速率限制採故障開放 (Fail-Open) 設計
每位使用者的速率限制器使用 Redis,但在 Redis 連線錯誤時採故障開放策略。此設計確保 Redis 暫時中斷不會阻擋合法的交易操作。WAF 層級的速率限制(每 IP、AWS 管理)提供後備保護。
TradingView 設定¶
Webhook URL¶
將 {account_webhook_token} 替換為控制面板中交易帳戶 → Webhook 設定顯示的 Token。
警報訊息(JSON 酬載)¶
{
"action": "{{strategy.order.alert_message}}",
"symbol": "TXFR1",
"quantity": {{strategy.order.contracts}},
"timestamp": "{{timenow}}",
"secret": "your-webhook-secret-here"
}
| 欄位 | 來源 | 說明 |
|---|---|---|
action |
{{strategy.order.alert_message}} |
TradingView 策略動作(buy、sell、close 等) |
symbol |
硬編碼或 {{ticker}} |
交易標的 |
quantity |
{{strategy.order.contracts}} |
口數/手數 |
timestamp |
{{timenow}} |
當前 UTC 時間(TradingView 內建變數) |
secret |
您的 Webhook 密鑰 | 在控制面板 → 交易帳戶 → Webhook 設定中取得 |
模擬模式
在 Webhook URL 後附加 ?simulation=true 即可在模擬模式下執行。模擬訂單會旁通時間戳記和密鑰驗證(第二至三層),且不會執行真實交易。
安全性比較¶
4pass Webhook 安全性與常見替代方案的比較:
| 功能 | 4pass | 一般 Webhook | 僅 API 金鑰 |
|---|---|---|---|
| 基於 URL 的帳戶隔離 | 有 每帳戶獨立 Token | 無 共用 URL | 無 |
| 時間戳記驗證 | 有 可設定視窗 | 無 | 無 |
| 密鑰認證 | 有 常數時間比對 | 無或基本認證 | 有 標頭 |
| 重放偵測 | 有 Redis + SHA-256 | 無 | 無 |
| 每位使用者速率限制 | 有 每分鐘 60 次滑動視窗 | 無 | 每金鑰 |
| IP 白名單 | 有 可選的每位使用者設定 | 無 | 無 |
| 即時攻擊警報 | 有 含上下文的電子郵件 | 無 | 無 |
| 稽核日誌 | 有 完整請求上下文 | 無 | 部分 |