上一篇用 Tool Poisoning 打了三關,只要改掉 MCP 的工具描述,就可能讓 Agent 把 email 和整段聊天紀錄塞進原本不該出現的參數。
我們在 AI 黑魔法(05) 裡把 AI 的攻擊面拆成資料、模型、應用與系統四塊,「系統」指的是撐起這一切的基礎設施,模型服務、API、雲端環境、權限與金鑰都在裡面。
這一層出的都是老問題,只是 AI 把它們放大了:
傳統的 API 被人狂打,吃掉的是頻寬跟主機資源,加資源就撐得住;模型 API 被人狂打,吃掉的是 GPU 時間,而且是按 Token 計費,多算的每一個字都會出現在帳單上。

使用者打進去的內容從瀏覽器送進 Web App,再經過 API Gateway 驗身分、給授權,才輪到 Agent 決定要不要碰 Tool、RAG、資料庫跟外部 API,模型推論也不是 Agent 自己算的,它往下呼叫 Inference API,Request 進 Queue,再交給 Container 裡的 GPU。
還有 Cache、Object Storage、Log、Tracing 跟 Billing,從瀏覽器到 GPU 每一段都會碰到,帳單就是一段一段加出來的,使用者平常碰得到的只有瀏覽器,接下來每換一次手,身分跟成本都可能被重新決定一次。
傳統 API 做 Rate Limit,最常見的方式就是限制每秒或每分鐘能送幾次 Request,一般服務這樣做通常可行,因為同一支 API 每次處理的工作量不會差太多。
但模型的 API 就不一定了,一句「你好」,跟一份快塞滿 Context 的文字,在 Gateway 看起來都是一個 Request,後面吃掉的資源卻完全不同,輸入越長,要處理的 Token 越多,記憶體和 GPU 時間也跟著往上,圖片與音訊還各自有額外的處理成本,成本也不是只看輸入,推理模型可能額外產生大量 Reasoning Token,請求失敗後可能自動 Retry,後面如果掛了 Agent,一次使用者操作甚至會繼續展開成多次模型呼叫和 Tool Call,這些全都算在同一次 Request 底下。
Request 可以怎麼把 Token 與 GPU 時間往上推,先看以下案例:
一位遊戲開發者在 Threads 分享,他替武俠 RPG 做了高智力的大儒 NPC,讓玩家向它請教治國策略、破解武功心法,有天半夜伺服器的 API Token 用量突然往上衝,查下去才看到玩家把微積分作業、Python 錯誤跟工作信件改寫成武俠用語,交給 NPC 背後的模型處理。
要把服務的資源吃光,也不一定得送超長的輸入,Sponge Examples 這篇研究找的就是那種「字不多,卻特別難算」的輸入,攻擊者根本不在乎模型答得對不對,只想讓同一次回答花更久的時間。
網頁端可能只限制字數,模型處理的卻是 Token,也就是文字被切碎之後的碎片數量。就像搬家公司報價只看紙箱數量,真正搬東西的工人在意的是重量,十箱餅乾和十箱啞鈴,箱數一樣,但搬起來完全是兩回事。
同樣一百個字,最後切出來的 Token 數不一定一樣,罕見的字、奇怪的符號、換一種語言,都可能被切得更碎,只拿字數當門檻,請求看起來沒有超標,GPU 時間和帳單卻可能翻上好幾倍。
OWASP LLM06:2026 Unbounded Consumption 定義的就是這種風險,系統允許過度且不受控的推論之後,攻擊者就有機會拖慢甚至癱瘓服務,把成本一路往上堆,還可能透過大量查詢去複製模型能力或竊取智慧財產。其中專門衝著帳單來的那一種有自己的名字,叫錢包阻斷服務(Denial of Wallet,DoW),服務沒有掛,帳單先爆掉。
那要怎麼限?
單次 Request:限制 Input Token、Output Token、圖片或音訊大小、最長執行時間以及單次可接受的成本。
單次 Agent Run:限制最多走幾步、最多呼叫幾次工具、總 Token 用量,以及整次執行可以花多少錢。Agent 如果開始重複相同步驟,也要能直接中止,OWASP 把這道機制叫做 Agentic Circuit Breaker。
Key、User 與 Tenant:分別限制 Token 使用速率、Concurrency 和每日或每月預算,不能只做全站共用額度,不然其中一個 Tenant 就可能把其他人的資源一起吃掉。
整個服務:限制 Queue 深度、GPU 使用量和總支出,超過門檻後要能拒絕新工作、降級服務,或把部分流量切到成本較低的模型。
以上四項都設好後,還有最後一件事要確認,限制到了,工作要真的停,使用者按了取消或直接關掉瀏覽器分頁,那個取消訊號要一路傳到後面真正在算的 Worker,畫面停了,GPU 還在燒,那只是把浪費藏到使用者看不見的地方。
Credential 一旦流出去,別人就能直接用你帳號裡的模型。Sysdig 在 2024 年把這種攻擊叫做 LLMjacking,AWS 也把它收進 Threat Technique Catalog。
Sysdig 那個案例是先從一台跑舊版 Laravel 的機器打進去(CVE-2021-3129),把那台機器上存的雲端 Credential 帶走,這種進來的方式跟 AI 一點關係都沒有,接下來能跑多久、能跑多兇,就看那組 Credential 的權限開得多大,還有沒有人在看模型的呼叫紀錄。

那要怎麼防?
Sysdig 那個案例裡,攻擊者拿到 Credential 之後沒有馬上跑推論,他先送一個一定會失敗的請求,看回來的錯誤是權限不足還是參數不合法,就知道自己叫不叫得動模型,再去查這個帳號有沒有把模型呼叫記下來,確認等一下大量跑的時候會不會留下紀錄。AWS 那份偵測建議還點了一個訊號,有人跑去申請把模型的用量上限開大,那通常代表他準備要大量跑東西了。
這些訊號要看得出來,前提是帳單拆得開,如果你只看得到整個雲端帳號這個月花了多少錢,那多出來的部分是用量真的長上來、是某個 Prompt 被改壞一直重試,還是 Credential 已經在別人手上,在帳單上長得一模一樣,帳單只告訴你多花了多少,沒告訴你多花在誰身上。每一次模型呼叫都要記下是誰發的,哪個使用者、哪個功能、走的是哪一段 Agent Workflow,也要記下它花了多少,用了哪個模型跟哪些 Tool、跑在哪個地區、吃掉多少 Token 跟 GPU 時間,這樣才算得出每個團隊、每個服務平常花多少。
有了平常的數字作為基準,異常才看得出來。某個 Workflow 突然一直吐超長的回答,多半是 Prompt 被改壞了;同一個 Tool 一直重試,問題通常在 Client 那邊。要是有個沒見過的身分在你根本沒開服務的地區大量呼叫,那就得當成 Credential 已經在別人手上來處理。預算告警要是接不到這些紀錄,它響的時候只代表錢花完了,你還是得從頭一個一個 Workflow 去找。
很多平台會讓不同 Tenant 共用同一個模型、同一批推論機器,模型本身不會記住誰是誰,記住東西的是旁邊的 Cache、連線池跟對話記憶。只要有一個沒把 Tenant 算進去,資料就會漏到別的 Tenant,或者漏給同一個 Tenant 裡沒有權限的使用者。
2023 年 3 月,OpenAI 因為 redis-py 的 Bug 把 ChatGPT 整個下線,他們用 Redis 存使用者資訊,省掉每個請求都去查一次資料庫,所有請求輪流借用同一個連線池的連線。有些請求會在讀到回應之前就取消了,那個還沒讀到的回應留在連線裡,接手同一條連線的下一個請求,就直接讀到別人的資料。有使用者因此看到別人的聊天標題,同一批受影響的 ChatGPT Plus 使用者裡,還有 1.2% 看得到別人的姓名、帳單地址跟信用卡末四碼。
連線池是 ChatGPT 那次下線出事的地方,Cache 一樣是同一批人共用的東西。Key 如果只放 Prompt 的 Hash,A 公司員工問過的問題,B 公司員工問一樣的內容,就會直接拿到 A 的答案。Key 裡要放 Tenant、使用者,還有他當下的授權範圍。RAG 也一樣,如果兩家公司的文件丟進同一個知識庫,查詢時沒先按 Tenant 篩選,B 公司的人問問題就可能連到 A 公司的內容。Object Storage 的路徑、對話記憶跟除錯用的 Log 也要照著 Tenant 分開放。資料進了 Cache 之後,權限還是每次讀寫都要重新看一次,不能當作前一層已經幫你檢查過。
要怎麼驗證 Tenant 之間真的隔開了?讓 Tenant A 產生不含機密的唯一字串當記號,再從 Tenant B 那邊一個一個找,搜尋、Cache、Log、匯出檔、錯誤訊息,連系統自動寄出去的信都要找,完全找不到才算過關。ChatGPT 那次下線,就是請求被取消才出事。同一套測試在重試、取消、逾時,跟 Worker 重複使用之後,都要再跑一次。
自己架模型服務的話,光是把模型包進 Container 還不夠,容器本來就只隔離得住 Process 跟檔案系統,用的還是同一顆 Host Kernel。Container 要摸得到 GPU,還得靠 Driver 跟幾支工具幫忙牽線,這幾支工具本來就會碰到 Host,一旦出了漏洞,攻擊者就可能從容器裡跳到 Host 上。
NVIDIA 在 2025 年 7 月公布 CVE-2025-23266,CVSS 9.0 的漏洞。這個漏洞後來被取名 NVIDIAScape,成因是 Container Toolkit 初始化容器的其中一支 Hook,沒有過濾就直接信任環境變數,攻擊者只要塞一個惡意映像檔、把 LD_PRELOAD 設成自己的函式庫,Hook 執行的時候就會把那支函式庫載進去,直接在 Host 上用 Root 權限跑起來,所以「模型已經放進 Container」只能算一種部署方式,不等於隔離做好了。
那還要補哪幾層呢?
automountServiceAccountToken: false,不要讓它預設掛著 Token。| 風險 | 最小可行的防護 | 沒做會發生什麼 |
|---|---|---|
| 一次請求就吃掉大量資源 | 單次 Request 限 Input Token、Output Token、執行時間與成本 | 一句「你好」和塞滿 Context 的請求,帳單可能差很多 |
| Agent 自己展開成一長串呼叫 | 限步數、工具呼叫次數與整次執行的預算,重複同一步就中止 | 使用者點一次,後面持續呼叫模型與 Tool |
| Credential 外洩後被拿去跑模型 | 前端不放 API Key,每支服務各用一組身分,只給需要的權限 | 別人用你的模型,帳單記在你頭上 |
| 錢在燒卻找不到是哪一段 | 同一次工作掛同一個關聯 ID,記下 Tenant、模型、Tool 與 Region | 只知道帳單變高,說不出該停掉哪個 Workflow |
| 取消了但工作沒停 | 取消訊號一路傳到 Worker,確認 GPU 真的釋放 | 畫面停了,GPU 還在燒 |
| 共用 Cache 與對話記憶跨 Tenant | Cache Key 帶上 Tenant、User 與授權範圍,每次讀寫重新判斷授權 | A 的內容出現在 B 的畫面上 |
| 自架的 Runtime 沒跟著修 | Runtime 元件納入 SBOM 與修補排程 | 容器邊界失效,工作負載跨到 Host |
如果真的要排順序,我會先處理 Credential 與預算。前端拿掉 API Key,每支服務拆開身分,只給它需要的權限,再替 Request、Agent、Key 與 Tenant 設下用量上限。Credential 外洩造成的損失最快,也最難事後補救,額度被吃完,服務就停了,帳單也已經產生。Tenant 隔離則要在上線前測好,Runtime 納入平常的修補排程,不能等出事才更新。
每一項做完都要實際測過。超過 Token 上限的 Request 會不會被擋下來、Agent 重複呼叫同一個 Tool 會不會停、使用者取消後 Worker 與 GPU 有沒有繼續跑、Tenant B 找不找得到 Tenant A 的資料,這些都是驗證時要看的結果。
設定、測試結果、Audit Log、監控圖表、SBOM 與版本紀錄都要留著,之後才查得到限制設在哪裡、現在還有沒有作用。不然報告上只寫「已加上 Rate Limit」,半年後通常連它卡在哪一層都找不到。
每次模型呼叫用了誰的權限、花了誰的預算、碰到誰的資料,限制到了有沒有真的停,這些都要查得到。下一篇來看下載回來的模型檔,Pickle 為什麼可能在載入時執行程式碼,以及換成 Safetensors 之後,還有哪些檔案能改掉模型的行為。