營運與事後:稽核、偵測、應變
保管別人的機密 — 第 5 篇:營運與事後
前四篇回答的是「事前怎麼實作」。但設計圖畫得再好,上線之後總會走樣,也總會出錯:某個權限慢慢設太寬、憑證清單漏了一把、某位工程師被釣魚。這篇回答第 1 篇開頭剩下的兩個問題:營運中怎麼定期稽核,以及出事之後怎麼發現、怎麼補救。最後用攻擊情境把整套設計驗收一次。
營運中:定期稽核
稽核的意思很單純:定期拿「設計上應該長怎樣」去對「實際上長怎樣」。前四篇已經留下了好幾份「應該長怎樣」的文件,稽核就是拿它們來對帳:
- 第 3 篇的權限表:誰能 Decrypt、誰能讀資料庫。
- 第 4 篇的憑證清單:每一把憑證的負責人、期限、輪換方式。
- 這篇後面的告警清單:每一條告警該在什麼情況響。
走樣通常不是一次大改造成的,而是一點一點累積:為了除錯臨時多開一個權限,事後忘了收回;新服務上線多了一把金鑰,沒人登記進清單;某條告警太吵被靜音,就再也沒打開。Zeabur 的管理權杖從 2024 年建立後沒有輪換過,八月事件後說「已輪換所有內部管理金鑰」,實際上只換了一把。這些都是只要定期對一次帳就會發現的事。
下面是一份可以直接照著排進行事曆的稽核表:
| 頻率 | 稽核項目 | 怎麼做 | 對照 |
|---|---|---|---|
| 每次 commit | 程式碼裡有沒有機密 | pre-commit 與 CI 跑 secret scanning | 第 4 篇:憑證不存在任何檔案裡 |
| 每週 | log、CI 變數、wiki 裡有沒有明文 | 用同一套掃描工具掃 log 與設定 | 第 3 篇:API 與 log 只經手密文 |
| 每月 | 權限對帳 | 匯出 KMS 規則與 IAM 設定,跟權限表比對;列出所有能 Decrypt 的身分 | 第 3 篇權限表 |
| 每月 | 憑證清單對帳 | 找出快過期、沒有負責人、屬於已停用服務的憑證 | 第 4 篇憑證清單 |
| 每季 | 輪換演練 | 挑一把憑證照 runbook 實際輪換一次,記錄花了多久、卡在哪 | 第 4 篇:每一把鑰匙都要能換 |
| 每季 | 告警演練 | 故意觸發一次,例如用非注入元件的身分呼叫 Decrypt,確認告警會響、有人接手 | 本篇告警清單 |
| 每季 | 還原演練 | 從備份還原一份資料庫,確認裡面只有密文,而且沒有 KMS 權限就解不開 | 第 2 篇、第 4 篇備份 |
| 每半年 | 管理權限複查 | 誰能申請管理權限、break-glass 帳號誰保管,離職或換組的人有沒有移除 | 第 4 篇控制面 |
| 看到別人的重大事件時 | 對照檢查 | 拿對方的事件經過,逐條問「如果發生在我們這裡,哪一層會擋住」 | 本篇最後的攻擊情境 |
最後一列,就是這個系列本身在做的事。別人的事件報告是最便宜的演練教材:對方已經付出代價,把攻擊者走過的每一步都寫出來了。
稽核的結果要留下紀錄:哪天做的、發現什麼、怎麼處理。下次稽核時先看上次的待辦有沒有關掉。
為什麼一定要有偵測
Zeabur 的時間軸裡,有兩個數字特別刺眼:
- 10 月 14 日,持續超過 16 小時的攻擊活動,沒有觸發任何告警。
- 從第一次已知的憑證濫用,到啟動調查,大約 36 小時。而啟動調查的原因,是收到使用者回報。
為什麼沒有被發現?報告給的答案是:攻擊者使用的是有效的憑證,走的是正式的存取管道。對系統來說,這些操作看起來跟平常的管理員一模一樣。
這點出偵測真正要做的事。只靠「擋下不認識的人」不夠,因為攻擊者拿的是認識的鑰匙。要能發現「認識的人做了不尋常的事」:不該在這時間出現的操作、不該出現的量、不該出現的來源。
該告警什麼
把主圖上每個關鍵步驟,都問一次「平常長什麼樣子、什麼樣子算異常」,就能得到一份告警清單:
| 告警 | 對應主圖 | 平常的樣子 | 異常的樣子 |
|---|---|---|---|
| 大量 KMS Decrypt | 第 12、13 步 | 跟容器啟動次數差不多 | 一小時內解開大量、或很多不同 org 的 DEK |
| 不是注入元件的身分呼叫 Decrypt | 第 12 步 | 不會發生 | 任何一次都要告警(就算被拒絕) |
| 容器內執行指令 | 第 15 步、容器 | 偶爾除錯,有申請紀錄 | 對使用者容器 exec、沒有對應的申請 |
| 管理權限的使用 | 控制面 | 有 JIT 申請、來自內網 | 沒有申請、陌生來源、非上班時間 |
| break-glass 帳號被使用 | 控制面 | 不會發生 | 任何一次 |
| 大量讀取資料庫 | 平台 DB | 應用程式的固定查詢 | 逐表全量讀取、內部帳號大量匯出 |
| 備份被列出或下載 | 備份 | 只有備份與還原流程 | 其他身分、陌生來源 |
| 內網加入新節點 | 網路 | 只有自動擴展 | 不在預期名單的節點 |
| 雲端金鑰從陌生來源使用 | 全部 | 固定的服務、固定的 IP 範圍 | 新的 IP、新的國家 |
拿這張表對照 Zeabur 事後宣布要新增的告警:管理權限的異常使用、在容器內執行指令、大量讀取機密或資料庫、內網加入新節點、雲端金鑰從陌生來源使用。幾乎一一對得上。換句話說,這些告警如果事前就有,這次事件很可能早得多被發現,而不是等 36 小時後的使用者回報。
告警的原則是寧可少而準。一天響幾百次的告警,跟沒有告警差不多,因為很快就沒有人會看。上表裡標「任何一次」的幾項,本來就不該發生,最適合先做。
紀錄本身也要保護
告警靠的是紀錄。Zeabur 的報告裡有一長串「無法確認」,原因幾乎都是紀錄不夠:
- 儲存空間沒有啟用物件存取紀錄,不知道哪些檔案被下載。
- 雲端專案的紀錄只記寫入、不記讀取,只能確認攻擊者改了什麼,不知道看了什麼。
- 資料庫稽核只記指令、不記輸出。
最後的結果是,很多範圍只能「視為外洩」。對使用者來說,這代表他們必須假設最壞的情況,把所有東西都換一輪。
所以紀錄要注意四件事:
- 讀取也要記,特別是機密、備份、KMS Decrypt。對機密保管服務來說,「誰讀了什麼」比「誰改了什麼」更重要。
- 紀錄送到另一個地方保存,例如獨立的雲端帳號、開啟防刪除的儲存空間。攻擊者拿到管理權限時,第一件事常常是清紀錄。
- 保存夠久。OWASP 建議至少可以查詢 90 天,更久的封存起來。
- 時間要準,所有系統對時,事後才拼得出時間軸。
外洩時的處理順序
假設告警響了,確認是真的外洩,接下來怎麼做?OWASP 的建議順序是:撤銷、輪換、清除副本。第 4 篇從 Zeabur 學到的教訓,要放在最前面:先切斷攻擊者的所有管道,否則輪換只是在替他準備新鑰匙。
- 切斷。撤銷已知外洩的權杖,關閉攻擊者用的入口(公網管理介面、SSH),停掉他可能讀得到的備份與儲存空間的存取。這一步越快越好,寧可先影響服務。
- 劃定範圍。用 KMS 的 Decrypt 紀錄,找出哪些 org 的機密被解開過;用控制面紀錄,找出哪些容器被 exec 過。紀錄完整的話,這一步能把「全部視為外洩」縮小成一份清單。
- 輪換平台自己的憑證。照第 4 篇的憑證清單逐一輪換,並確認新憑證不會落到攻擊者還拿得到的地方。
- 清除副本。舊值可能還留在程式碼、log、備份、工程師的筆電裡,一一清掉。
- 通知使用者,請他們到源頭更換。這一步常被低估:平台換不了使用者的機密。
DATABASE_URL裡的密碼要到 Org A 自己的資料庫去改,OpenAI 金鑰要到 OpenAI 後台去撤銷。平台能做的是盡快、盡量精確地告訴使用者哪些要換。 - 復盤。這次哪一層失守、為什麼沒有被告警抓到,回頭補進前面幾篇的檢查清單。
第 5 步的代價,Zeabur 的使用者是實際承擔的。報告提到,事件後有一位使用者的 OpenAI 金鑰被濫用,約 1,107 次圖片生成請求,推測就是來自這次外洩。機密外洩的損失,最後常常落在使用者身上,而不是平台身上。這也是為什麼前面每一層都值得認真做。
驗收:用攻擊情境走一遍
最後,把前四篇的設計放在一起,用六種攻擊情境檢查。點選情境,主圖上被攻破的參與者會標成紅色,下面說明攻擊者拿到什麼、結果如何、靠哪一層擋下,以及 Zeabur 這次的對照。
- 攻擊者拿到
- 所有紀錄的密文,和封好的 DEK。
- 結果
- 沒有 KMS 的 Decrypt 權限,兩樣都打不開。
- 靠哪一層
- 儲存層:信封加密。KEK 只在 KMS 裡,不跟資料放在一起(第 2 篇)。
- Zeabur 這次
- 平台資料庫裡 3,218,965 筆環境變數存的是明文,讀到就等於拿到。
六個情境看下來,可以分成三類:
- 擋得住的:A 平台 DB、B 備份、C 內部帳號。攻擊者拿到的都是存起來的東西,加密加上權限就能擋下。
- 損害有限的:Secret service 被入侵。寫入端拿不到 Decrypt 權限,只有入侵期間新送進來的機密會外洩。
- 要靠偵測的:KMS 權限設太寬、D 控制面權杖外洩。這兩種是「拿到了合法的鑰匙」,設計只能縮小範圍、縮短時間,最後要靠這篇講的告警及早發現。
回頭看三個設計目標
第 1 篇定了三個目標,現在可以對答案了:
| 目標 | 靠哪幾篇做到 |
|---|---|
| 拿到資料庫,不等於拿到機密 | 第 2 篇:信封加密,資料庫和備份裡只有密文 |
| 能解開機密的身分越少越好,每次解開都留紀錄 | 第 3 篇:寫入與讀出分開、Decrypt 只給注入元件、workload identity、KMS 稽核 |
| 每把鑰匙都會過期,外洩時換得掉,被濫用時看得到 | 第 4 篇:短期權杖、憑證清單、輪換;第 5 篇:定期稽核、告警、紀錄、應變順序 |
營運與事後檢查清單
- [ ] 稽核表已排進行事曆,每一項都有負責人
- [ ] 每次稽核留下紀錄,上次的待辦有追蹤
- [ ] 上表標「任何一次」的告警已經設好:非注入元件呼叫 Decrypt、break-glass 被使用
- [ ] 大量 Decrypt、容器內 exec、管理權限使用、備份存取都有告警
- [ ] 機密、備份、KMS 的讀取操作都有紀錄
- [ ] 紀錄送到獨立帳號、防刪除、保存至少 90 天
- [ ] 有寫好的應變手冊:切斷 → 劃定範圍 → 輪換 → 清除副本 → 通知使用者 → 復盤
- [ ] 定期演練一次,確認告警真的會響、紀錄真的查得到
結語
讀 Zeabur 的報告時,最大的感受是:每一個問題單獨看都不罕見。明文存資料庫、權杖沒設期限、管理介面開在公網、備份金鑰權限太大,很多團隊身上都找得到其中一兩項。真正的問題是它們同時存在,而且沒有任何一層告警,於是一個入口一路通到底。
反過來說,這個系列整理的每一項做法,也沒有一項是新發明。信封加密是雲端 KMS 的標準用法,寫入與讀出分開是 Fly.io 公開的架構,權限、生命週期、稽核、應變,OWASP 早就整理成清單。總而言之,要做一個合格的機密保管服務,難的不是知道該怎麼做,而是每一層都真的做到,上線之後定期確認它還成立,並且在某一層失守時,下一層還站得住。
對我來說,這也是讀別人事件報告的意義。不是為了指出誰做錯,而是拿來檢查自己:事前能不能實作好、營運中有沒有在稽核、出事之後知不知道怎麼補救。
參考資料
設計與實作:
- OWASP Secrets Management Cheat Sheet:最接近 cookbook 的一份,涵蓋加密、權限、生命週期、稽核、偵測、事故應變。
- Google Cloud KMS:Envelope encryption:DEK 與 KEK 的標準流程。
- AWS Secrets Manager:Secret encryption and decryption:每次更新機密都用新的 data key。
- Fly.io:Secrets:API 只能加密、主機 agent 才能解密、機密讀不回來。
- Kubernetes:Using a KMS provider for data encryption:用信封加密保護 etcd 裡的 Secret。
- GitHub REST API:Actions secrets:送出前用 repository 公鑰與 LibSodium 加密。
- Tink:Client-side encryption with a cloud KMS:
KmsEnvelopeAead,不用自己拼信封加密。 - NIST 名詞:Key-wrapping key:KEK 的正式定義,出自 SP 800-57。
事件:
- Zeabur 2026 年 10 月資安事件初步報告:本系列引用的事實都出自這份報告(2026 年 10 月 22 日讀取,報告標示仍可能更新)。Zeabur 已宣布將於 2026 年 12 月 31 日結束營運,原頁面之後可能無法存取,因此我們製作了一份備份版本(PDF,2026 年 10 月 22 日存檔),作為文章情境的補充。