查核資訊: 本文於 2026-08-25 查核 OWASP LLM10:2025 Unbounded Consumption、MITRE CWE-770 與 RFC 6585,並引用同日完成的離線資源控制實驗。攻擊手法、模型計費方式、tokenizer、服務商限制與 HTTP 實作仍可能改變;正式系統套用前,請重新確認實際模型、流量、成本與基礎設施政策。
LLM 應用程式如果只限制每分鐘的請求數,仍可能讓一筆超長輸入占用大量運算資源,也可能讓多筆尚未完成的推論同時占滿可用的模型執行資源。系統若等到模型回覆後才記帳,多筆已進入推論的請求仍可能共同超過預算。
阻斷服務攻擊(Denial of Service,DoS)不一定會讓服務完全無法使用。攻擊者也可能延長正常使用者的等待時間、持續消耗付費 token,或迫使系統在尖峰期間拒絕正常流量,進而影響可用性與成本。
Day 28 把請求速率、輸入 token、輸出 token、總 token、並行數與預算拆成六道控制。實驗使用七組互相獨立的虛構事件,每組事件都分別進入完全不設限的路徑,以及依固定順序執行六道控制的路徑。固定 token 數量只用來驗證控制流程,不代表任何真實模型的 tokenizer 或帳單。
OWASP LLM10:2025 Unbounded Consumption把過量且不受控制的推論列為 LLM 應用風險。攻擊者可以大量送出請求、增加輸入長度、要求更長輸出,或反覆執行耗用資源的操作,造成阻斷服務、成本增加與服務品質下降,也可能利用大量查詢複製模型行為。
MITRE CWE-770描述的是更一般的資源限制問題:系統替使用者或服務配置資源時,沒有設定數量或大小上限。攻擊者便可能持續占用 CPU、記憶體、連線、執行緒或其他共享資源,讓其他使用者無法取得所需資源。CWE-770 建議設定每位使用者的資源限制、明確定義最大值,並在架構中加入節流機制。
LLM 推論讓系統需要同時限制更多資源。一筆 request 可能占用 API gateway、應用程式執行緒、佇列、模型執行資源與連線;輸入和輸出 token 會影響推論時間與費用;工具呼叫或重試還可能把一次使用者操作放大成多次下游工作。
因此,系統不能只問「一分鐘幾次」,還要回答「每次最多多大」、「同時最多幾筆」、「一段期間最多花多少」以及「超過限制後在哪個位置停止」。
Token 是模型把文字切分後處理的單位,不等於固定數量的字元。不同模型與斷詞器(tokenizer)可能把同一段文字切成不同的 token 數量。正式系統必須使用實際模型對應的 tokenizer,或採用服務商提供的 token 計數結果,不能直接用字數代替 token 數。
Day 28 的六道控制如下:
| 控制 | 檢查內容 | 檢查時機 | 主要保護目標 |
|---|---|---|---|
| 請求速率 | 同一主體在固定期間內送出的 request 數 | 收到 request 時 | API 入口、應用程式與整體吞吐量 |
| 輸入 token | 輸入 token 是否超過單筆上限 | 建立模型 request 前 | 前處理、記憶體與推論時間 |
| 輸出 token | 要求保留的最大輸出 token 是否超過上限 | 建立模型 request 前 | 推論時間與單筆最高成本 |
| 總 token | 輸入加最大輸出是否超過單筆上限 | 建立模型 request 前 | 單筆工作的總資源占用 |
| 並行數 | 同一主體尚未完成的 request 數 | 進入佇列或模型執行資源前 | 模型執行資源、連線與等待時間 |
| 預算 | 已用額度加保留額度是否超過期間上限 | 模型推論前 | 租戶、專案與整體成本 |
這六道控制不能互相取代。每分鐘五次的速率限制仍可能放行五筆超長 request;單筆 token 上限也無法阻止攻擊者同時送入數百筆符合大小限制的工作。並行數可以限制正在執行的工作,但如果完成後立即接受下一筆,仍需要速率與預算限制處理長時間累積。
Day 28 的完整控制路徑固定使用以下順序:
request 到達
→ 計入速率
→ 檢查輸入 token
→ 檢查輸出 token 上限
→ 檢查輸入加最大輸出的總 token
→ 檢查尚未完成的並行 request
→ 保留預算
→ 接受 request
這個順序先執行不需要模型的整數比較,再決定是否保留並行名額與預算。應用程式若先呼叫模型,再檢查 token 或預算,拒絕動作發生時成本已經產生。
速率計數會包含遭 token、並行數或預算限制拒絕的嘗試。否則,攻擊者可以持續送出超大 request,讓系統反覆執行解析、驗證與 token 計數,卻完全不消耗速率額度。正式系統是否計入所有失敗請求,仍要依登入、健康檢查與可信內部流量設計例外,不能直接套用單一規則。
預算控制如果只累加已完成 request 的實際 token,並行請求就可能一起穿過檢查。假設剩餘額度是 100,兩筆各自最多需要 80 token 的 request 同時到達;兩筆都看到尚未扣款的 100,便可能同時被接受,最後消耗 160。
Day 28 在接受 request 時先保留「輸入 token 加最大輸出 token」,完成後再用「輸入 token 加實際輸出 token」結算,並釋放沒有使用的保留額度。這種做法可以避免並行 request 共用同一份尚未扣除的預算。正式系統還必須把讀取餘額、檢查上限與保留額度做成一次不可分割的更新,或放在同一筆交易中完成;否則多個執行個體仍可能同時讀到相同餘額。
預算也不一定等於 token。不同模型、批次、快取、工具與供應商可能使用不同費率;組織可以先把每個 request 換算成內部成本單位,再依使用者、租戶、專案、功能與全系統設定不同上限。Day 28 只使用固定 token 額度,沒有換算真實貨幣。
如果把所有攻擊放進同一條連續事件流,速率限制可能先拒絕後面的 request,導致 token、並行數與預算控制沒有機會執行。Day 28 因此使用七組獨立案例;每組都從空白狀態開始,並在同一個固定預算期間內完成。
固定政策如下:
| 政策 | 上限 |
|---|---|
| 請求計算期間 | 60,000 毫秒 |
| 每個主體最多 request 數 | 5 |
| 單筆輸入 token | 100 |
| 單筆最大輸出 token | 100 |
| 單筆總 token | 150 |
| 每個主體同時執行的 request | 2 |
| 預算期間 | 86,400,000 毫秒 |
| 每個主體的 token 預算 | 300 |
兩條路徑都讀取相同事件。無限制路徑(unbounded)記錄每筆 request 的資源需求,但不拒絕任何 request;完整控制路徑(layered_controls)依固定順序執行六道控制。實驗不呼叫模型,也不使用真實 tokenizer;案例中的 token 數量是實驗資料(fixture)事先設定的整數。
| 案例 | 事件設計 | 事前預測 |
|---|---|---|
| 正常連續請求 | 兩筆 request 各自完成 | 兩條路徑都接受 2 筆 |
| 請求速率突增 | 60 秒內送出 7 筆,每筆立即完成 | 無限制接受 7 筆;完整控制接受 5 筆 |
| 輸入 token 耗盡 | 輸入 101、最大輸出 10 | 完整控制以輸入上限拒絕 |
| 輸出 token 保留 | 輸入 10、最大輸出 101 | 完整控制以輸出上限拒絕 |
| 總 token 耗盡 | 輸入 80、最大輸出 80 | 完整控制以總量上限拒絕 |
| 並行數突增 | 三筆 request 依序進入但都未完成 | 無限制同時保留 3 筆;完整控制只保留 2 筆 |
| 預算耗盡 | 四筆各保留並使用 100 token | 無限制使用 400;完整控制使用 300 |
完整實驗程式、政策與案例雜湊、原始結果雜湊,以及排除 request ID 與 subject ID 後的統計,都已固定在 Day 28 不可變更的證據版本。
正式實驗在兩條路徑各處理 19 筆 request。結果如下:
| 路徑 | 收到 | 接受 | 拒絕 | 接受的估算 token | 已結算 token | 尚未釋放的保留 token | 最高並行數 | 預算超額 |
|---|---|---|---|---|---|---|---|---|
| 無限制 | 19 | 19 | 0 | 1,132 | 625 | 442 | 3 | 100 |
| 完整控制 | 19 | 12 | 7 | 590 | 495 | 40 | 2 | 0 |
完整控制把接受的估算 token 從 1,132 降為 590,差異是 542。這個數字是固定案例中被拒絕 request 的輸入加最大輸出 token 總和,不是模型實際節省的 token,也不是帳單金額。
無限制路徑尚有 442 token 處於保留狀態,因為三個超過 token 上限的案例與並行案例都沒有完成事件。完整控制只留下並行案例中兩筆已接受 request 的 40 token 保留額度。這項差異顯示單看已結算 token 會漏掉尚在執行或排隊的資源承諾。
完整控制的七筆拒絕分布如下:
| 拒絕原因 | 次數 | 對應案例 |
|---|---|---|
| 請求速率上限 | 2 | 7 筆突增請求中的第 6、7 筆 |
| 輸入 token 上限 | 1 | 輸入為 101 的 request |
| 輸出 token 上限 | 1 | 最大輸出為 101 的 request |
| 總 token 上限 | 1 | 輸入 80 加最大輸出 80 |
| 並行數上限 | 1 | 第 3 筆尚未完成的 request |
| 預算上限 | 1 | 第 4 筆需要 100 token 的 request |
正常案例的兩筆 request 都被接受。七個案例的結果全部符合事前預測。這代表固定驗證器正確執行這份政策與事件順序,不代表上限數值適合真實產品。
只限制輸入 token 會留下輸出端風險。攻擊者可以送入很短的提示,卻要求模型產生很長的內容;如果應用程式允許使用者直接指定 max_tokens 或類似參數,就必須在應用程式內重新套用上限,不能把使用者值原樣傳給模型服務。
只限制輸出 token 也不夠。超長輸入仍會占用解析、token 計數、記憶體與模型 context。應用程式應在組合完整的模型 request 後計算輸入 token,因為 system prompt、檢索內容、對話歷史與工具 schema 都會增加輸入量。超過上限時,系統應拒絕 request,或依任務定義的規則縮減內容,不能直接從任意位置截斷。
總 token 上限處理兩者的組合。Day 28 的總量限制規定,輸入 token 加最大輸出 token 不得超過 150。因此輸入 80、最大輸出 80 的 request 即使分別低於 100,仍會因總量 160 被拒絕。
速率限制只計算一段時間內進入多少 request,不知道每筆工作多久完成。五筆各需一秒的 request 與五筆各需一分鐘的 request,在同一個速率計數中可能完全相同,實際占用的模型執行資源與連線卻差很多。
Day 28 的並行案例連續送入三筆 request,而且不提供完成事件。無限制路徑的最高並行數是 3;完整控制接受前兩筆,第三筆因每個主體最多同時執行 2 筆而被拒絕。這項結果只驗證計數器,沒有測試真實的模型執行資源、佇列等待或取消操作。
正式系統除了設定並行數上限,也要決定 request 遭拒絕、排隊、逾時或由使用者中斷時如何釋放名額。系統如果只在正常回覆時釋放名額,例外、斷線與執行程序停止都可能讓名額持續被占用。分散式系統還要處理重複完成事件、設定名額的到期時間,並在執行個體失聯後回收名額。
RFC 6585 第 4 節定義 429 Too Many Requests,表示使用者在一段期間內送出太多請求。回應可以包含 Retry-After,告訴用戶端多久後再試。
RFC 6585 沒有規定伺服器如何識別使用者,也沒有規定要依單一資源、整台伺服器或多台伺服器共同計數。LLM 應用程式通常需要同時考慮帳號、API key、租戶、IP、裝置、功能與全系統容量。只依單一 subject ID 計數,攻擊者可能輪替帳號或憑證繞過限制。
RFC 6585 也提醒,大量攻擊發生時,替每一筆 request 產生 429 回應本身也會消耗資源。API 入口或負載平衡器可能需要提早丟棄連線、限制回應內容,或在流量進入應用程式前執行封鎖。系統不能因為回傳了正確的狀態碼,就認定已經完成 DoS 防護。
Day 28 的計數器以虛構 subject ID 為單位,方便單獨驗證控制。正式系統至少要決定以下範圍是否各自需要上限:
身分驗證可以讓計數器有較穩定的主體,但不能單獨阻止濫用。合法帳號可能遭接管,付費使用者也可能消耗超過系統可承受的資源。資源上限必須獨立於功能授權存在。
用戶端收到 429 或逾時後如果立即重試,可能讓尖峰流量持續更久。服務應提供有上限的重試次數與退避策略,並加入隨機延遲,避免大量用戶端在同一時間再次送出 request。伺服器也要確保重試不會重複扣款、重複執行工具或建立相同外部動作。
佇列可以暫時吸收短時間的流量尖峰,但沒有長度上限的佇列,只會把阻斷服務問題改成記憶體耗盡與長時間等待。系統要限制佇列長度、每個主體的排隊數與最長等待時間,並在工作已經失去處理價值時取消。模型已開始串流後,使用者中斷連線也不代表供應商一定停止推論;應用程式需要確認取消訊號實際傳到下游。
Agent 流程還要限制每次 request 可以觸發的模型回合、工具呼叫、檢索次數與重試次數。單一外部 request 即使通過速率限制,也可能在內部展開成數十次昂貴操作。Day 28 沒有工具與 Agent 迴圈,因此不宣稱已測試這種放大。
Day 27 已建立 request ID、政策版本、決策與原因代碼。Day 28 的控制可以沿用這份事件契約,記錄 request_rate_limit、input_token_limit、output_token_limit、total_token_limit、concurrency_limit 與 budget_limit 等固定原因。
紀錄不應包含完整 prompt。系統通常只需要能關聯同一主體的內部識別碼、計數範圍、政策版本、估算 token、保留額度、結算額度、拒絕原因與 request ID;這些識別碼仍要依敏感資料管理,不能因為沒有保存姓名就視為匿名資料。團隊還要監控拒絕率、最高並行數、佇列長度、預算使用速度、保留額度長時間未釋放,以及全系統與租戶層級的剩餘容量。
拒絕數突然增加不一定代表攻擊。新功能、錯誤的重試設定、tokenizer 變更或政策上限過低也會造成相同現象。告警必須連到可以調查 request 類型、政策版本與部署變更的處理流程。
這次實驗只有七組固定事件、兩條記憶體內路徑與一份政策。每條路徑處理 19 筆 request;每組案例都會清空計數器,並在同一個固定預算期間內執行。實驗沒有測試期間切換、額度結轉、夏令時間或時鐘回撥。
案例中的 token 是 fixture 宣告的整數。實驗沒有使用 tokenizer,沒有建立 system prompt、對話歷史、檢索內容或工具 schema,也沒有測試串流輸出與實際停止生成。1,132、590、625、495 與 442 都是固定事件的資源單位,不是服務商帳單或真實模型測量值。
實驗只用單一執行程序更新記憶體計數器,沒有測試多個執行個體同時更新、不可分割的資料更新、分散式鎖、計數器同步延遲、跨區域網路中斷或狀態遺失。完整控制路徑的預算超額為 0,但這項結果不能用來判定相同程式碼在分散式環境中是否會超額保留預算。
實驗沒有測試共用憑證、帳號輪替、IP 變更、殭屍網路、快取、佇列、模型執行服務、GPU、供應商配額、重試風暴、模型回合、工具呼叫或下游依賴。它也沒有測試限制值對正常流量的誤傷、不同租戶的公平性或緊急容量保留。
所有 subject ID、request ID 與 token 數量都是版控內的虛構資料。模型呼叫、網路呼叫、外部副作用與真實帳務事件都是 0。原始結果保留在 Lab 中不納入 Git 的目錄;公開證據只包含案例 ID、統計、拒絕原因、雜湊與限制。
Day 28 的無限制路徑接受 19/19 筆 request;完整控制路徑接受 12/19,並在模型呼叫前拒絕 7 筆。接受的估算 token 從 1,132 降為 590,最高並行數從 3 限制為 2,固定預算超額從 100 降為 0。七組案例全部符合事前預測。
這些結果說明六道控制各自處理不同資源。請求速率限制進入頻率,輸入與輸出上限限制單筆大小,總 token 限制兩者組合,並行數限制同時占用,預算則限制一段期間內的累積承諾。系統還要加入多範圍計數、有限佇列、取消、退避、不可分割的額度更新與持續監控,才能把固定實驗轉成正式控制。
下一篇會進入 AI Red Teaming 實戰,讓 garak 與 PyRIT 對同一個本機應用程式端點執行自動化攻擊測試。Day 28 的速率、並行數與預算限制會保留在端點前方,避免測試工具本身製造不受控制的流量。
本文同步刊載於作者的個人 Blog:閱讀原文。