教學版:四道防線只要做到一道,
這 15 天就不會發生
- 一句話結論
- 一把 2020 年建立、六年沒換的雲端金鑰檔,同時放在程式碼庫與部署平台裡。8/27 部署平台出事,36 小時內就有陌生人拿它操作我們的雲端專案,一用就是 15 天。
- 他要的是什麼
- 他要的不是你的資料,是一個權限全開、沒人看帳單的雲端專案:先試 AI 的使用額度能不能轉賣,再備幾台空機器交給別人用。
- 你今天做一件事
- 四道防線只要做到一道就擋得住:不發金鑰檔、程式帳號不給最高權限(Owner)、關掉用不到的雲端服務、設預算警示。這次四道都沒有。 直接去做 →
- 哪些能信
- 3 可證明2 推論3 缺口 每個主張都標了等級,缺口是誠實寫出來的。
- 破口速查
- 受害端一律去識別化;寫「未公開」代表公開資料查不到。
- 伺服器服務商
- Google Cloud(GCP)。攻擊者用我們的專案開了 3 台虛擬機,並盜用 AI 模型服務 Vertex AI;金鑰的副本放在部署平台的環境變數與程式碼庫裡
- 網路服務商
- Google Cloud 的預設內部網路。攻擊者自己加了一條對整個網際網路開放的防火牆規則;網路連線紀錄(VPC Flow Logs)沒開
- 漏洞出現的地方
- 身分/金鑰層:2020 年建立、六年沒換的金鑰檔(Service KEY JSON)被放進程式碼庫與部署平台的環境變數,而這個程式帳號是專案最高權限 Owner
- 馬上自我檢測
gcloud asset search-all-iam-policies --scope=projects/PROJECT_ID --query='policy:(roles/owner OR roles/editor) memberTypes:serviceAccount'- 漏洞修補方式
- 用組織政策禁止建立服務帳戶金鑰,或強制金鑰到期;程式改靠檔案分享取得權限、不給任何專案層角色;關掉業務用不到的運算與 AI 服務(compute/aiplatform API);每個專案設預算警示
有 15 分鐘再看:其餘 2 點
- 攻擊者第一個動作不是開虛擬機,是測試 AI 模型服務(Vertex AI)上哪些模型能用;9/3 才開防火牆與 3 台虛擬機,前 2.5 天閒置,之後被拿去大量下載,收進 3.2 TB。
- 帳單 US$146.10 每一塊錢都對得上,14 筆稽核記錄完整;但機器裡做了什麼、資料有沒有被讀、流量去哪,因為三種紀錄都沒開,永遠答不出來。
同一起事件的教學版:15 天逐分鐘攤開,每一步標出為什麼會成功、哪一道控制擋得下來。不發金鑰、不給 Owner、關掉沒用的 API、預算警示,四道都沒有。
本篇目錄
把那十五天逐分鐘攤開
2026 年 9 月 12 日 20 點 27 分,服務帳戶開始回 invalid_grant: account not found。第一個猜測是有人誤刪了帳戶,實際上是整個專案被 Google 停權。
被拿走的是一把 2020 年建立的 Google Service KEY JSON。它被 commit 進至少兩個 repo,也放進部署平台的環境變數;8/27 平台出事,36 小時內就有人拿它去簽 token。接下來兩週,攻擊者測了 Vertex AI 上的模型、替我們的專案接受模型使用條款、開了一條對全網開放的防火牆規則,並在同一秒送出 5 台 e2-standard-4。三台成功的機器 9 天收進 3,186 GiB、送出 284 GiB,同期 Gemini 成功呼叫 212 次。
這是內部教學版,所以每一步都會標出 MITRE ATT&CK 技術編號、當時留下的證據、為什麼能成功,以及哪一道控制擋得下來;文末附鑑識指令與討論題。要先記住的是這個故事的形狀:**它不是一個「被駭」的故事,是「權限給太大、金鑰活太久、沒人看帳單」三件事同時成立的故事。**時間一律為台灣時間(UTC+8),整理於 2026-09-15,這篇教學版於 2026-09-17 去識別化後發布。
事件前,主控台上看起來完全健康
沒有 VM、沒有對外服務、每天費用 $0。系統是一支訊息機器人加上幾支排程,跑在部署平台上;資料落在 Google 試算表與 Drive,陳列照片上傳到 Drive,對外服務只有 Bot 的 webhook。所有 Google 端的動作都由同一個 GCP:Service Account 完成。
放大器在權限。這個服務帳戶掛著 roles/owner,專案內另有 4 個服務帳戶掛著 Owner 或 Editor,全專案共 7 把使用者建立的金鑰。Compute Engine API 是我方 2026-03 為了一次測試自己開的,之後沒關;Vertex AI API 更早就開著。
防護只有 Google 預設給的那一層:Cloud Audit Logs 的 Admin Activity 預設開啟、保留 400 天、不用錢。沒有預算警示、沒有 VPC Flow Logs、沒有資料存取稽核、沒有 Vertex AI 請求記錄、沒有任何記錄型警示。
同一次平台事件裡外洩的憑證有三類,第一列是這篇的主角。
| 服務 | 憑證類型 | 用途 | 存在形式 | 放在哪 | 權限範圍 | 狀態 |
|---|---|---|---|---|---|---|
| GCP IAM 服務帳戶(GCP:Service Account) | 使用者管理金鑰:Google Service KEY JSON | 訊息機器人讀寫 Google 試算表、上傳陳列照片到 Drive 的排程 | JSON 檔(含 private_key 的 PEM 私鑰、client_email、private_key_id);在部署平台則是整段 JSON 塞進一個環境變數,或 base64 後再塞 | 至少兩個 repo 的同一個目錄(commit 進 git 歷史)+部署平台服務的環境變數 | roles/owner(整個專案) | 2026-09-13 刪除金鑰、拿掉 Owner;金鑰 2020-09 建立,6 年未輪替 |
| AWS IAM 使用者 | access key(AKIA… + secret) | 應用程式存取 S3/SES 等 AWS 服務 | 明文環境變數(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY) | 部署平台服務的環境變數 | 依各 IAM policy | 2026-08-29~30 已輪替(平台清單內) |
| MySQL/PostgreSQL 資料庫 | 帳號密碼 | 各服務連線資料庫 | 明文環境變數或連線字串(DATABASE_URL) | 部署平台服務的環境變數 | 該資料庫全部資料 | 2026-08-29~30 已輪替(平台清單內) |
| 其他符合憑證格式的環境變數 | 不明(平台通知只說「符合憑證格式」) | 不明 | 明文環境變數 | 部署平台 | 不明 | 未逐一確認;平台未提供清單 |
同一把 KEY JSON 存在三個地方(兩個 repo 的 git 歷史、部署平台的環境變數),而 8/29 的輪替只照平台的環境變數清單做,前兩個地方沒有人想到。憑證清冊要以「身分」為單位,不是以「存放位置」為單位。 這條線的完整復盤見〈清單全部打勾的第 13 天,整個專案被停權了〉。
事件時間線:最該停下來看的是中間那段斜線區
- 8/27部署平台通知環境變數外洩;我方依平台清單輪替 AWS 金鑰與資料庫密碼平台通知信與輪替紀錄。
- 8/27(推定)Google Service KEY JSON 隨環境變數被取走無直接證據,只有時間相關性。
- 8/28 23:00攻擊者首次使用金鑰(Vertex AI RawPredict)Cloud Audit Logs:callerIp 93.123.109.230。
- 8/29 03:21攻擊者替專案接受 Model Garden 使用條款Audit Logs:AcceptPublisherModelEula。
- 9/3 12:38–12:40開防火牆、同一秒送出 5 台 VM,3 台成功Audit Logs:firewalls.insert、instances.insert ×5(2 台 ZONE_RESOURCE_POOL_EXHAUSTED)。
- 9/5 20:00VM 開始被大量使用,一直到 9/11Cloud Monitoring:每 6 小時流入從 3 GiB 暴增到 100 GiB 以上。
- 9/7 → 9/12Gemini GenerateContent 成功 212 次Monitoring api/request_count 依 credential_id。
- 9/12 20:27Google 停權整個專案服務帳戶回 invalid_grant;VM 狀態 TERMINATED。
- 9/13刪除專案內 7 把使用者金鑰、拿掉 5 個服務帳戶的 Owner/EditorAudit Logs:DeleteServiceAccountKey、SetIamPolicy。
- 9/14 → 9/15送出申訴,隔日解封申訴單與解封通知。
教學上最該停下來看的是 8/29 到 9/13 之間那一段斜線區:我們已經知道平台出事、也已經動手輪替,但這把金鑰還能用。8/29~8/30 的輪替只處理了清單上的 AWS 金鑰和資料庫密碼,以 KEY JSON 形式散在 repo 裡的那一把,一直到 9/13 才失效——從通知算起,17 天。
攻擊鏈:一把鑰匙同時打開三條路
外洩的 KEY JSON 到了攻擊者主機(93.123.109.230,AS48090 主機商,自動化工具),用私鑰換成 token,之後所有動作都以 GCP:Service Account 的身分進行。因為它是 roles/owner,三條路同時開著:
| 路 | 起點 | 做了什麼 | 結果 |
|---|---|---|---|
| Vertex AI | 8/28 起 | 試呼叫 Claude 27 次(404/429)→ 接受 Model Garden 使用條款 → Gemini 成功 212 次 | US$2.79 |
| Compute Engine | 9/3 起 | 防火牆對全網開 22、3000–9999 → 5 台 VM 同時開,3 台成功 → 開機腳本開啟 root 密碼 SSH → 收進 3.2 TB、送出 284 GiB | US$143.32 |
| Sheets/Drive | 不明 | 可讀所有分享給這個帳戶的檔案 | 沒開資料存取記錄,無法排除被讀 |
第三列是「有能力但沒有記錄可以確認」,這一列會在後面變成一個永遠答不出來的問題。按 ATT&CK 排開,七個階段是這樣:
表上放不進去的細節在下面。取得金鑰那一步完全沒有記錄可看,只有時間差:這把金鑰 2020 年建立,之後被 commit 進 repo、又被放進環境變數;它在 repo 裡躺了好幾年都沒被濫用,卻在平台出事後 36 小時內開始被用。它之所以成立,是因為一份長期有效的私鑰檔被複製到多個地方,而沒有人知道全部的位置在哪;能擋住它的只有「不發長期金鑰」這一件事。
登入那一步也不需要闖關。私鑰簽 JWT 換 token,沒有主控台、沒有密碼、沒有 MFA 可以攔。他第一件事是找 AI 模型,Claude 全部回 404 或 429,隔天凌晨就改成替我們的專案接受模型使用條款——那一筆記錄長這樣:
2026-08-28T19:21:46Z ModelGardenService.AcceptPublisherModelEula principal GCP:Service Account callerIp 93.123.109.230
這一步能成功,是因為 Vertex AI API 開著、而帳戶是 Owner,什麼都能呼叫;擋它的是關掉用不到的 API,加上讓 Bot 只靠檔案分享、不給任何專案角色。9/3 的探查與開防火牆同理:專案保留了預設網路與預設防火牆規則,Compute Engine API 為了一次測試開了之後沒關。要注意的是攻擊者從頭到尾沒有呼叫 EnableService——400 天的稽核記錄可以證明這一點——不過 Owner 本來就有權限開任何 API,所以就算當時是關的也只是拖延。
開 VM 那一步最值得逐欄看,因為一個 instances.insert 請求就把一切都帶齊了:
| 欄位 | 攻擊者的設定 | 用意 |
|---|---|---|
| zone | us-central1-a/b、us-east4-a、us-east1-b/c,同一秒送出 5 台 | 分散地區,某區資源不足或被封也有別台能用 |
| machineType | e2-standard-4(4 vCPU、16GB),標準計費、非搶佔式 | 便宜又不會被中途回收 |
| disks | Ubuntu 24.04 官方映像檔,200GB pd-balanced | 放得下大量下載的檔案 |
| networkInterfaces | default 網路、外部 IP(Premium tier)、網路標記 crusader | 讓前一步開的防火牆規則套用上去 |
| serviceAccounts | 不掛 | 不需要,也避免留下更多 GCP 記錄 |
| metadata | 開機腳本;enable-oslogin=FALSE | 開機自動植入 root 密碼登入 |
us-east1 的兩台回 ZONE_RESOURCE_POOL_EXHAUSTED 失敗,另外 3 台成功。開機腳本只做三件事,這裡只講形狀不貼指令:替 root 設一組密碼並解鎖帳號、改 sshd 設定允許密碼與 root 直接登入再重啟 ssh、對外查自己的公網 IP 並印一行「idle-vm ready」帶著 IP 回報。腳本名稱就叫 idle-vm,裡面沒有挖礦程式,只是把機器變成「可以用 root 密碼登入」並回報位址,也就是備好一台空機器交給別人用。這一步之所以沒有驚動任何人,是因為沒有配額上限也沒有預算警示;把 CPU 配額調到 0、對帳單帳戶設預算警示、監控 instances.insert 並通知,三者任一都會在那一秒響。
機器開好後的用途,可以從流量的形狀推出來。前 2.5 天幾乎閒置,CPU 2–3%、每 6 小時只有約 3 GiB 進出,是備好的空機器在等人接手;9/5 20:00 起流量突然暴增,CPU 升到 10–30%,一直持續到 9/11,其中 9/11 08–14 時單一時段收進 486 GiB。三台合計收比送多 11 倍——單純當代理的話進出量會差不多,所以這比較像是機器被交給別人拿去大量下載,而下載的內容從 GCP 外部完全看不到。GCP 流入流量不收費,所以帳單上只看得到送出的部分。VM-2 和 VM-5 在 9/11 20:00 左右停止活動,VM-1 一直用到停權;9/8 起攻擊者每小時列一次 VM 清單確認機器還活著。同一段期間(9/7 00:00~9/12 21:00)他改用 Gemini,混用 2.5 Pro、3.x Pro、3.6/3.7 Flash,文字和圖片都送。這六天之所以能跑滿,是因為一個平常沒有任何計費的專案每天突然多出 $12~21,而沒有人在看它的帳單。
關鍵證據:控制平面完整,主機層只剩磁碟
攻擊者在 GCP 上的每一個「動作」都查得到;在 VM 裡面做了什麼、下載了什麼,控制平面查不到。把事後最想問的八個問題排成一張表,界線很清楚,而唯一還能補救的來源是那 3 顆磁碟,刪之前一定要先做映像。
| 要回答的問題 | 還原程度 | 資料來源 | 保留期限 |
|---|---|---|---|
| 建了什麼、改了什麼權限 | 完整:時間、IP、金鑰、完整參數 | Admin Activity 稽核記錄 | 400 天 |
| 呼叫了哪些 API、AI 模型 | 只有次數、方法、地區,沒有內容 | Monitoring api/request_count | 6 週 |
| VM 用了多少資源 | 每台 CPU、收送流量曲線 | Monitoring VM 指標 | 6 週 |
| 誰 SSH 進來、執行了什麼、下載了什麼 | 目前沒有;磁碟鑑識可能查到 | 3 顆 200GB 磁碟的 auth.log、journal、shell 歷史、殘留檔案 | 刪除即消失 |
| 流量從哪裡來、傳到哪裡 | 無 | VPC Flow Logs 沒開 | — |
| AI 提示詞和回應 | 無 | Vertex AI 請求記錄沒開 | — |
| 試算表、Drive 有沒有被讀 | GCP 端無 | 資料存取稽核沒開;可再查 Workspace 管理控制台的 Drive 記錄 | — |
| 金鑰怎麼外洩的 | 只能推定 | 需要部署平台提供事件細節 | — |
表上「完整」的那幾列,背後是三條可以證明的證據;「無」的那幾列,就是後面三條缺口。
三條可證、兩條推論、三條缺口,全部都是用下面這幾道唯讀查詢拉出來的,PROJECT_ID 與 ATTACKER_IP 換成你的就能在自己的環境重跑一次。
gcloud logging read 'protoPayload.methodName="v1.compute.instances.insert"' \ --project=PROJECT_ID --freshness=45d \ --format="table(timestamp,protoPayload.authenticationInfo.serviceAccountKeyName,protoPayload.requestMetadata.callerIp)"
gcloud logging read 'protoPayload.requestMetadata.callerIp="ATTACKER_IP"' \ --project=PROJECT_ID --freshness=400d
gcloud logging read 'protoPayload.methodName=~"EnableService"' --project=PROJECT_ID --freshness=400d gcloud logging read 'protoPayload.methodName="SetIamPolicy"' --project=PROJECT_ID --freshness=10d --format=json
API 呼叫次數依憑證分組,用 Cloud Monitoring 的 serviceruntime.googleapis.com/api/request_count,以 resource.label.credential_id 與 resource.label.method 分組;主控台的 API 用量頁看不到依憑證的分組。VM 流量曲線用 compute.googleapis.com/instance/network/received_bytes_count 與 sent_bytes_count,每 6 小時 ALIGN_SUM。
根因分析:教學上最容易停在第一層
表面原因是一把 2020 年建立的 KEY JSON 被外部拿去簽 token,開了 3 台 VM、呼叫了 212 次 Gemini。停在這裡,結論會變成「我們被駭了」,然後什麼都不會改。
往下一層是技術:只需要讀寫試算表的服務帳戶掛著 roles/owner,憑證是沒有到期日、可無限複製的長期私鑰檔;專案同時開著業務用不到的 compute 與 aiplatform API,還保留了預設網路與預設防火牆。再往上是管理:8/27 的輪替範圍照平台的環境變數清單走,repo 與本機副本沒有人負責;這個專案平常費用是 $0,沒有人固定看帳單,也沒有人負責它的安全狀態。這兩層都有稽核記錄可以證明。
第四層只能寫成推論,因為它本來就無從舉證:缺少以身分為單位的憑證清冊、金鑰到期機制與高權限帳戶定期複核,也沒有組織政策限制金鑰建立與可用地區。六年前為了方便做的一個決定,一路帶到今天沒有人複查。課堂上請學員自己把這四層寫一次,會發現前兩層修完,下一個專案照樣會長出一把 Owner 金鑰。
訊號有六條,最早一條在第一天
8/28 晚上 11 點,陌生 IP 以 Bot 帳戶呼叫 Vertex AI:callerIp 一直都在稽核記錄裡,但沒有人看,也沒有任何以 principal 為條件的告警。隔天凌晨 3 點 21 分,有人替專案接受 AI 模型使用條款;AcceptPublisherModelEula 是一個專案大概一輩子只會出現一次的方法名,卻沒有被列進記錄型警示。
9/3 12:40 那一秒,新防火牆規則對全網開放、五台 VM 同時送出,但沒有 instances.insert/firewalls.insert 的即時通知,Security Command Center 的高風險發現也沒有人訂閱。隔天帳單就動了:平常 $0 的專案一天花 $13,而帳單帳戶沒有任何預算警示,異常狀況頁也要有人主動打開才看得到。
9/5 20:00,三台 VM 收進的流量從每 6 小時 3 GiB 暴增到 100 GiB 以上——這三台不是我們開的,不在任何儀表板與資產清單上,沒有基準線可以比。9/7,Bot 帳戶的 API 用量出現 aiplatform 與 compute,這是全案最有辨識力的一條,可惜依 credential_id 分組的用量只在 Cloud Monitoring 查得到,主控台的 API 用量頁沒有這個維度。
六條訊號,沒有一條會自己來找人。這一節適合當隨堂題:把六條發給學員,問他們各自要在哪個介面、設哪一條規則才會亮。
事件處置:偵測那一格不是我們的功勞
9/12 20:27,排程回報 invalid_grant: account not found,一開始以為服務帳戶被誤刪,追查後才確認整個專案被 Google 停權,三台不認識的 VM 狀態是 TERMINATED。偵測來源是業務中斷加上別人的濫用偵測,不是我們的任何一條告警。停權同時造成業務影響:所有靠這把金鑰讀寫試算表、上傳陳列照片的排程一起停擺。
9/13 的第一個動作是圍堵:刪除專案內全部 7 把使用者建立的金鑰,拿掉 5 個服務帳戶的 Owner/Editor,讓所有位置的副本一次失效。有一件事刻意不做——沒有刪除服務帳戶本身,它擁有大量 Drive 照片,刪除會連檔案一起消失。
同一天做清除:先對攻擊者留下的 3 顆 200 GB 磁碟建立快照保存證據,再做離線解剖;同時確認沒有受管執行個體群組與執行個體範本,代表刪掉 VM 就會永久停止。鑑識完成前磁碟保留,所以帳單持續累積。9/14 送出申訴,隔日解封;停擺的排程先緊急改用其他服務帳戶,之後再切到無金鑰代理。業務中斷約 3 天。
做得好的四件事都免費,做不好的五件事也不貴
這次做對的四件事都是零成本的決定:Admin Activity 預設開啟、保留 400 天,所以 14 筆記錄把控制平面的每一步都留下來了,連請求參數都在;停權當天沒有急著刪 VM,先對磁碟建快照,之後才有離線解剖可做;撤銷用的是「刪光專案內全部使用者金鑰」,不是只刪已知的那一把;最後是沒有刪除擁有大量 Drive 檔案的服務帳戶本身,避免把業務資料一起刪掉。
該改的有五件。8/27 的輪替照平台環境變數清單做,漏掉 repo 裡的 KEY JSON,金鑰因此多活 17 天;資料存取稽核、VPC Flow Logs、Vertex AI 請求記錄三種日誌都沒開,最想回答的三個問題全部無解;沒有預算警示,帳單從 $0 漲到一天 $21,連續 9 天沒有人知道;Compute Engine API 為了一次測試開啟後沒有關掉,等於替攻擊者把變現管道先鋪好;最後一件是演練層面的——停權後排程全部停擺才發現沒有備援身分,只能在事故中緊急換帳戶。
改善措施:四道防線,這次一道都沒有
當天就該完成的四件:刪除專案內全部使用者金鑰、拿掉服務帳戶的 Owner/Editor(9/13 完成),並用舊憑證驗證它真的回 invalid_grant;先為 3 顆磁碟建快照做鑑識,再刪除攻擊者留下的 VM、磁碟與 crusader-allow-ssh 防火牆規則;停用該專案的 Compute Engine 與 Vertex AI API;帳單帳戶設預算警示,每個專案一條,門檻用平常費用的 2 倍。
一個月內:建立不需私鑰的試算表代理服務,讓應用程式不必持有 KEY JSON;所有專案開啟資料存取稽核記錄(至少 Sheets、Drive、Storage)與 VPC Flow Logs;對 instances.insert、firewalls.insert、SetIamPolicy、CreateServiceAccountKey、AcceptPublisherModelEula 設記錄型警示;另一個仍是 Owner、金鑰仍有效的服務帳戶比照處理。
這一季:組織政策禁止建立服務帳戶金鑰,例外要審核、並加上金鑰到期時數;憑證清冊改以身分為單位;組織政策限制可用地區、禁止外部 IP、限制可用 AI 模型,不用 VM 的專案把 CPU 配額設 0;把「不要刪除擁有大量 Drive 檔案的服務帳戶本身」寫進處置手冊,避免事故中誤操作。
這些措施的骨架只有四道防線,任一道有做,損害都會小很多。這次四道都沒有。
| 防線 | 做法 | 設定方式 |
|---|---|---|
| 1. 金鑰根本不存在 | 不發服務帳戶私鑰;應用程式改走代理,只拿可撤銷的呼叫 token;repo 掃描並清除歷史裡的 KEY JSON | 組織政策 iam.disableServiceAccountKeyCreation;例外專案加 iam.serviceAccountKeyExpiryHours |
| 2. 外洩了也沒權限 | 服務帳戶一律不給 Owner/Editor | 每月跑一次 asset search-all-iam-policies 找 roles/owner 或 roles/editor 的 serviceAccount |
| 3. 有權限也做不了大事 | 關掉用不到的 API;限制可用地區和 AI 模型;不用 VM 的專案把 CPU 配額設 0 | gcloud services disable compute.googleapis.com;組織政策 gcp.resourceLocations、compute.vmExternalIpAccess、vertexai.allowedModels |
| 4. 出事馬上知道 | 每個專案設預算警示;高風險動作設記錄型警示;有在跑服務的專案開資料存取稽核與 VPC Flow Logs | gcloud billing budgets create;警示條件 methodName 比對 instances.insert、firewalls.insert、SetIamPolicy、CreateServiceAccountKey、AcceptPublisherModelEula |
還有一條跨在四道防線之外的流程:任何平台通知外洩,24 小時內以「身分」為單位輪替全部憑證,包含散落在 repo 和本機的 KEY JSON,不是只照平台列的環境變數清單。下面這張表把這次的帳攤到每一個 SKU,最後三列是算不出金額、只能標成缺口的部分。
| 項目 | 用量 | 單價 | 期間 | US$ | NT$ | 舉證 |
|---|---|---|---|---|---|---|
| E2 vCPU(Americas) | 1,790.19 vCPU·小時 | ≈ $0.0218/vCPU·小時 | 9/3 → 9/12 | 39.05 | 1,250 | 帳單 SKU:E2 Instance Core running in Americas |
| E2 vCPU(Virginia) | 895.1 vCPU·小時 | ≈ $0.0246/vCPU·小時 | 9/3 → 9/12 | 21.99 | 704 | 帳單 SKU:E2 Instance Core running in Virginia |
| E2 記憶體(Americas) | 7,160.77 GiB·小時 | ≈ $0.0029/GiB·小時 | 9/3 → 9/12 | 20.93 | 670 | 帳單 SKU:E2 Instance Ram running in Americas |
| E2 記憶體(Virginia) | 3,580.4 GiB·小時 | ≈ $0.0033/GiB·小時 | 9/3 → 9/12 | 11.79 | 377 | 帳單 SKU:E2 Instance Ram running in Virginia |
| pd-balanced 磁碟 200GB × 3 | 124.05 + 62.16 GiB·月 | ≈ $0.10/GiB·月 | 9/3 → 9/14(未刪,持續計費) | 19.89 | 636 | 帳單 SKU:Balanced PD Capacity ×2 區 |
| 對外流量(4 個 SKU) | 229.9 GiB 送出;流入 3.2 TB 不計費 | $0.08–0.12/GiB | 9/5 → 9/12 | 24.80 | 794 | 帳單 SKU:Internet Data Transfer Out ×3、Carrier Peering ×1 |
| Compute Engine 其餘 212 個 SKU | 外部 IP、其他區流量等 | — | 9/3 → 9/12 | 4.87 | 156 | 帳單依服務小計 143.32 減前 10 名 SKU |
| Vertex AI/Gemini(27 個 SKU) 原價 3.62,優惠折抵 0.83 | 212 次成功呼叫;文字+圖片輸入輸出 | 依模型 token 計價 | 9/7 → 9/12 | 2.79 | 89 | 帳單依 SKU 分組;Monitoring api/request_count 依 credential_id |
| Networking(負載平衡/位址) 原價 2.02,優惠全額折抵 | — | — | 9/3 → 9/12 | 0.00 | 0 | 帳單依服務分組 |
| 磁碟未刪前的後續費用 估;GCP 到 9/15 的預估總額為 192.84 | 600 GB pd-balanced | ≈ $0.10/GB·月 | 9/15 起每月 | 60.00 | 1,920 | 牌價推算;VM 已 TERMINATED 但磁碟仍在 |
| 鑑識與申訴人力 估 | 約 1 人日 | 顧問日費 NT$20k–35k | 9/13 → 9/15 | 6251094.00 | 200,035,008 | 工時紀錄 |
| 專案停權 3 天的營運中斷 | 所有靠這把金鑰的排程停擺 | — | 9/12 → 9/15 | — | — | 無法量化,僅有排程停擺紀錄 |
| 試算表/Drive 被讀取的資料 | 不明 | — | 8/28 → 9/12 | — | — | 資料存取稽核沒開,無法舉證 |
| VM 收進的 3.2 TB 內容與去向 | 3,186 GiB | — | 9/5 → 9/12 | — | — | VPC Flow Logs 與 OS 日誌皆無;只剩磁碟 |
| 合計(僅可證明列) | 146.11 | 4,676 | ||||
US$146.10 之所以只是三位數,是因為攻擊者只開了 3 台中型 VM。同樣的權限換成 GPU 機型或更多地區,兩週可以燒掉幾萬美元。這次是運氣好,不是防禦好。
學習重點(對應模組 M0、M2、M3)
先讓學員站到他的位置看一次。他先測 AI 模型,因為 Claude token 有現成黑市,比開 VM 更快變現,不行才退而求其次;開 VM 時不掛服務帳戶、不用 gcloud、user-agent 只有 gzip(gfe),為的是留最少的指紋;同一秒開 5 台分散在 3 個區域,是因為某區配額不足或被封還有別台;腳本叫 idle-vm,說明他不是要用這台機器,是要把它交給下一個人;9/8 起每小時列一次 VM,則是在確認資產還活著。這是在經營,不是在搗亂。
- 01權限決定損害上限。攻擊鏈裡「提權」那一格是空的,不是因為我們擋住了,是因為一開始就給滿了。
- 02金鑰的壽命決定機會窗口。2020 年建立、沒有到期日的私鑰,2026 年一樣簽得出 token,而且不會有人收到通知。
- 03輪替的單位是身分,不是位置。平台清單只能說明那個平台現在存了什麼,repo 與本機不在它的視野裡。
- 04帳單是最便宜的一道偵測線。平常 $0 的專案 9/4 那天花了 $13,這條線不需要任何資安知識就看得懂。
- 05日誌的價值在事後。三種沒開的日誌,讓「資料有沒有被讀」這個客戶一定會問的問題,永遠只能回答「不知道」。
課堂討論題接在這五條後面,每題都沒有標準答案,目的是讓學員把結論搬到自己的環境:
- 8/27 收到平台通知後,我們依環境變數清單逐一輪替。為什麼這把 GCP 金鑰沒有被列進去?如果重來一次,清冊要怎麼建?
- 一個只讀寫試算表的 Bot 帳戶,為什麼會是 Owner?回想你負責的專案,有沒有「當初為了方便先給 Editor」的帳戶?
- 攻擊者第一個動作是找 AI 模型,不是開 VM。這對我們「哪些 API 應該開著」的判斷有什麼影響?
- 如果攻擊者開的是 GPU 機型,要多久會被 Google 停權?在那之前,我們的哪一個設定會先響?
- VM 從我們的專案收進 3.2 TB。如果下載的是盜版或違法內容,來源 IP 會追到我們。我們手上的證據足以證明不是自己下載的嗎?還缺什麼?
討論完,用下面十個關鍵字當作結業檢查:每一個都要能用自己的話講出「為什麼這篇非懂它不可」。
- 01roles/owner一把只寫試算表的金鑰為什麼能開 VM、叫 AI:因為帳戶是 Owner。權限決定損害上限。
- 02Google Service KEY JSON長期有效的私鑰檔,複製到哪裡就在哪裡;外洩不是「會不會」,是「哪一份」。
- 03以身分為單位輪替照平台的環境變數清單輪替會漏掉 repo 裡的 KEY JSON;清冊要以「這個身分有幾份憑證」建。
- 04Workload Identity讓程式不需要持有私鑰就能叫 API,是「金鑰根本不存在」這道防線的實作。
- 05AcceptPublisherModelEula攻擊者第一個成功的動作:替你接受模型條款。這個方法名出現在 Bot 帳戶下就是警訊。
- 06instances.insert開 VM 的稽核事件名;一批多區同時、無服務帳戶、enable-oslogin=FALSE 是攻擊者的指紋。
- 07credential_idMonitoring 的 api/request_count 可依憑證分組;主控台看不到,這是唯一能看出「哪把金鑰在叫什麼」的方法。
- 08預算警示(budget alert)平常 $0 的專案一天花 $13,9/4 就該響。這是四道防線裡最便宜的一道。
- 09Admin Activity 400 天 vs 資料存取稽核前者預設開、保留 400 天,所以攻擊者每個動作都查得到;後者預設關,所以資料有沒有被讀永遠不知道。
- 10idle-vm備好空機器交給別人用。攻擊者不是在用你的機器,是在經營你的機器。
如果重來一次:這是演練時照著跑的腳本
把 9/12 20:27 當成第 0 分鐘。
前十五分鐘先保存會變的東西:匯出帳單「依服務/依 SKU 分組」報表與 Monitoring 的 VM 流量曲線,後者只保留 6 週;用 gcloud logging read 以陌生 callerIp 與金鑰 ID 拉 Admin Activity,把不是自己人的動作列出來(這份保留 400 天,不急但先拉);再列出專案裡所有 VM、磁碟與防火牆規則,標出哪些不是我們建的。
第二個十五分鐘止血:刪除該身分的所有使用者金鑰,不是只刪已知的那一把,同時拿掉 Owner/Editor;用舊憑證呼叫一次,看到 invalid_grant 才算撤銷完成;記得不要刪除服務帳戶本身,它可能擁有 Drive 檔案。
剩下的半小時處理證據與退路:對每一顆磁碟先建快照再談刪除,快照是唯一能回答「VM 裡發生什麼」的來源;確認受管執行個體群組與執行個體範本都是空的,才代表刪 VM 就永久停止;刪除對 0.0.0.0/0 開放的防火牆規則,並清點專案裡其他相同形狀的規則;最後確認業務排程有沒有備援身分可以先接手,避免停權期間完全停擺——這一條是這次真正被打到的地方。
一小時之後的慢工:開資料存取稽核與 VPC Flow Logs,補上這次三題無解的來源;每個專案一條預算警示,並把五個高風險方法名設成記錄型警示;把應用程式換成不持有私鑰的代理架構,憑證清冊改以身分為單位。最後把這份腳本變成一次桌上演練:找一個人扮 9/12 的排程失敗訊息,看團隊多久走到撤銷那一步。
回到 9 月 12 日晚上 20 點 27 分那一行 invalid_grant。唯一一次有系統主動告訴我們出事的,是 Google 的濫用偵測,而不是我們自己的任何一條線。下一次它會不會一樣準時,這題沒有答案;能決定的只有一件事——在它響之前,我們自己有沒有先響。
演練之前,先用下面五步唯讀檢查自己的環境,十五分鐘做得完。
全部唯讀,不改任何設定;做完你會知道自己有沒有同樣的破口。
- 找出所有是 Owner 或 Editor 的服務帳戶
gcloud asset search-all-iam-policies --scope=projects/PROJECT_ID \ --query='policy:(roles/owner OR roles/editor) memberTypes:serviceAccount'看到什麼代表什麼:任何一筆都是紅燈。Bot 帳戶不該出現在這裡。 - 列出每個服務帳戶的使用者金鑰與建立日期
gcloud iam service-accounts keys list --iam-account=SA_EMAIL --managed-by=user \ --format='table(name,validAfterTime)'看到什麼代表什麼:validAfterTime 超過 90 天的,當成已外洩處理;這次那把是 6 年。 - 查 45 天內誰開過 VM、誰改過防火牆
gcloud logging read 'protoPayload.methodName=~"instances.insert|firewalls.insert"' --freshness=45d \ --format='table(timestamp,protoPayload.authenticationInfo.principalEmail,protoPayload.requestMetadata.callerIp)'看到什麼代表什麼:callerIp 不是你們辦公室或 CI 的 IP,或 principal 是 Bot 帳戶,就是本案 9/3 那一秒的畫面。 - 看帳單帳戶有沒有任何預算警示
gcloud billing budgets list --billing-account=BILLING_ACCOUNT_ID看到什麼代表什麼:空的代表沒有人會在 9/4 收到通知。每個專案至少一條,門檻用平常費用的 2 倍;名字照本案取「budget-2x-f4k」,收到通知信一眼就知道是哪一條在響。 - 在 repo 歷史裡找 KEY JSON
git grep -l '"type": "service_account"' git log --all -p -S'"private_key"' --name-only | grep -E '\.json$' | sort -u看到什麼代表什麼:任何命中都當已外洩:金鑰進過 git 歷史就拿不回來,要輪替不是刪檔。
環境背景、時間線、攻擊鏈對照 ATT&CK、關鍵證據、根因、處置與檢討都在完整版,約 14 分鐘。
本文由 AI 團隊協助研究與撰寫,經隱私與事實檢查後發布;證據等級依來源可信度標註。
複製下面這段,貼給你在用的 AI 工具(Claude Code、Cursor、Copilot、ChatGPT 都可以)。它會用本篇的破口位置、自我檢測與學習重點,對你的專案或環境做一次資安掃描,列出潛在問題與建議的調整或加強方式。
預設是唯讀:只發現問題、提出建議,不會改你任何檔案或設定。
M2 身分與金鑰 · M3 雲端層偵測 · M0 駭客經濟學 · 想在自己的環境做一次同樣的盤點,從 自我盤點 開始。
4 題單選,答對每題 +10 分、全對再 +10 分。加入 LINE 官方帳號、在 LINE 裡作答,成績與積分直接存入;在網頁作答會拿到一組序號,傳到 LINE 就能累積。
1 分鐘重點回饋 →每篇新文章一封:三點摘要、關鍵字定義、今天能做的一件事。或先 加入會員 記錄你完成的任務。