教學版:四道防線只要做到一道,
這 15 天就不會發生

速讀:5 分鐘掌握重點
一句話結論
一把 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 點
  1. 攻擊者第一個動作不是開虛擬機,是測試 AI 模型服務(Vertex AI)上哪些模型能用;9/3 才開防火牆與 3 台虛擬機,前 2.5 天閒置,之後被拿去大量下載,收進 3.2 TB。
  2. 帳單 US$146.10 每一塊錢都對得上,14 筆稽核記錄完整;但機器裡做了什麼、資料有沒有被讀、流量去哪,因為三種紀錄都沒開,永遠答不出來。

同一起事件的教學版:15 天逐分鐘攤開,每一步標出為什麼會成功、哪一道控制擋得下來。不發金鑰、不給 Owner、關掉沒用的 API、預算警示,四道都沒有。

GCP 服務帳戶金鑰外洩(教學個案)·2026-08-28 → 09-12·全文 14 分鐘
本篇目錄
VMAISheetsIAMroles/owner · 只需要寫試算表

把那十五天逐分鐘攤開

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 請求記錄、沒有任何記錄型警示。

同一次平台事件裡外洩的憑證有三類,第一列是這篇的主角。

leaked credentials這次外洩了什麼:服務、用途、存在形式
服務憑證類型用途存在形式放在哪權限範圍狀態
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 policy2026-08-29~30 已輪替(平台清單內)
MySQL/PostgreSQL 資料庫帳號密碼各服務連線資料庫明文環境變數或連線字串(DATABASE_URL)部署平台服務的環境變數該資料庫全部資料2026-08-29~30 已輪替(平台清單內)
其他符合憑證格式的環境變數不明(平台通知只說「符合憑證格式」)不明明文環境變數部署平台不明未逐一確認;平台未提供清單
憑證本身已遮蔽或失效;這裡只說明它是哪個服務、拿來做什麼、以什麼形式存放,讓你能對照自己的環境。

同一把 KEY JSON 存在三個地方(兩個 repo 的 git 歷史、部署平台的環境變數),而 8/29 的輪替只照平台的環境變數清單做,前兩個地方沒有人想到。憑證清冊要以「身分」為單位,不是以「存放位置」為單位。 這條線的完整復盤見〈清單全部打勾的第 13 天,整個專案被停權了〉。

事件時間線:最該停下來看的是中間那段斜線區

  1. 8/27
    部署平台通知環境變數外洩;我方依平台清單輪替 AWS 金鑰與資料庫密碼
    平台通知信與輪替紀錄。
  2. 8/27(推定)
    Google Service KEY JSON 隨環境變數被取走
    無直接證據,只有時間相關性。
  3. 8/28 23:00
    攻擊者首次使用金鑰(Vertex AI RawPredict)
    Cloud Audit Logs:callerIp 93.123.109.230。
  4. 8/29 03:21
    攻擊者替專案接受 Model Garden 使用條款
    Audit Logs:AcceptPublisherModelEula。
  5. 9/3 12:38–12:40
    開防火牆、同一秒送出 5 台 VM,3 台成功
    Audit Logs:firewalls.insert、instances.insert ×5(2 台 ZONE_RESOURCE_POOL_EXHAUSTED)。
  6. 9/5 20:00
    VM 開始被大量使用,一直到 9/11
    Cloud Monitoring:每 6 小時流入從 3 GiB 暴增到 100 GiB 以上。
  7. 9/7 → 9/12
    Gemini GenerateContent 成功 212 次
    Monitoring api/request_count 依 credential_id。
  8. 9/12 20:27
    Google 停權整個專案
    服務帳戶回 invalid_grant;VM 狀態 TERMINATED。
  9. 9/13
    刪除專案內 7 把使用者金鑰、拿掉 5 個服務帳戶的 Owner/Editor
    Audit Logs:DeleteServiceAccountKey、SetIamPolicy。
  10. 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 AI8/28 起試呼叫 Claude 27 次(404/429)→ 接受 Model Garden 使用條款 → Gemini 成功 212 次US$2.79
Compute Engine9/3 起防火牆對全網開 22、3000–9999 → 5 台 VM 同時開,3 台成功 → 開機腳本開啟 root 密碼 SSH → 收進 3.2 TB、送出 284 GiBUS$143.32
Sheets/Drive不明可讀所有分享給這個帳戶的檔案沒開資料存取記錄,無法排除被讀

第三列是「有能力但沒有記錄可以確認」,這一列會在後面變成一個永遠答不出來的問題。按 ATT&CK 排開,七個階段是這樣:

發生了什麼 · 8/28 23:00· MITRE ATT&CK T1078.004
用私鑰簽 JWT 換 access token,以 GCP:Service Account 的身分直接呼叫 API。沒有主控台登入、沒有密碼、沒有 MFA 這一關。第一件事不是開 VM,而是在 us-central1、us-east5、europe-west1/4 呼叫 Vertex AI 上的 Claude 模型(RawPredict),全部回 404 或 429。
當時留下的訊號
Cloud Audit Logs:callerIp 不是我們的、principal 是 Bot 帳戶、方法是 aiplatform。
能擋下它的控制
關掉用不到的 API;Bot 帳戶只給需要的角色。讀寫試算表其實不需要任何專案角色。

表上放不進去的細節在下面。取得金鑰那一步完全沒有記錄可看,只有時間差:這把金鑰 2020 年建立,之後被 commit 進 repo、又被放進環境變數;它在 repo 裡躺了好幾年都沒被濫用,卻在平台出事後 36 小時內開始被用。它之所以成立,是因為一份長期有效的私鑰檔被複製到多個地方,而沒有人知道全部的位置在哪;能擋住它的只有「不發長期金鑰」這一件事。

登入那一步也不需要闖關。私鑰簽 JWT 換 token,沒有主控台、沒有密碼、沒有 MFA 可以攔。他第一件事是找 AI 模型,Claude 全部回 404 或 429,隔天凌晨就改成替我們的專案接受模型使用條款——那一筆記錄長這樣:

Admin Activity 稽核記錄(節錄)READ-ONLY
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 請求就把一切都帶齊了:

欄位攻擊者的設定用意
zoneus-central1-a/b、us-east4-a、us-east1-b/c,同一秒送出 5 台分散地區,某區資源不足或被封也有別台能用
machineTypee2-standard-4(4 vCPU、16GB),標準計費、非搶佔式便宜又不會被中途回收
disksUbuntu 24.04 官方映像檔,200GB pd-balanced放得下大量下載的檔案
networkInterfacesdefault 網路、外部 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_count6 週
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 換成你的就能在自己的環境重跑一次。

誰開了 VM、用哪把金鑰、從哪個 IPREAD-ONLY
gcloud logging read 'protoPayload.methodName="v1.compute.instances.insert"' \
--project=PROJECT_ID --freshness=45d \
--format="table(timestamp,protoPayload.authenticationInfo.serviceAccountKeyName,protoPayload.requestMetadata.callerIp)"
攻擊者 IP 的所有動作(Admin Activity 保留 400 天)READ-ONLY
gcloud logging read 'protoPayload.requestMetadata.callerIp="ATTACKER_IP"' \
--project=PROJECT_ID --freshness=400d
攻擊者有沒有自己開 API;權限被怎麼改過READ-ONLY
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 配額設 0gcloud services disable compute.googleapis.com;組織政策 gcp.resourceLocations、compute.vmExternalIpAccess、vertexai.allowedModels
4. 出事馬上知道每個專案設預算警示;高風險動作設記錄型警示;有在跑服務的專案開資料存取稽核與 VPC Flow Logsgcloud billing budgets create;警示條件 methodName 比對 instances.insert、firewalls.insert、SetIamPolicy、CreateServiceAccountKey、AcceptPublisherModelEula

還有一條跨在四道防線之外的流程:任何平台通知外洩,24 小時內以「身分」為單位輪替全部憑證,包含散落在 repo 和本機的 KEY JSON,不是只照平台列的環境變數清單。下面這張表把這次的帳攤到每一個 SKU,最後三列是算不出金額、只能標成缺口的部分。

cost這次盜用的帳:用量、單價、期間成本
項目用量單價期間US$NT$舉證
E2 vCPU(Americas)1,790.19 vCPU·小時≈ $0.0218/vCPU·小時9/3 → 9/1239.051,250帳單 SKU:E2 Instance Core running in Americas
E2 vCPU(Virginia)895.1 vCPU·小時≈ $0.0246/vCPU·小時9/3 → 9/1221.99704帳單 SKU:E2 Instance Core running in Virginia
E2 記憶體(Americas)7,160.77 GiB·小時≈ $0.0029/GiB·小時9/3 → 9/1220.93670帳單 SKU:E2 Instance Ram running in Americas
E2 記憶體(Virginia)3,580.4 GiB·小時≈ $0.0033/GiB·小時9/3 → 9/1211.79377帳單 SKU:E2 Instance Ram running in Virginia
pd-balanced 磁碟 200GB × 3124.05 + 62.16 GiB·月≈ $0.10/GiB·月9/3 → 9/14(未刪,持續計費)19.89636帳單 SKU:Balanced PD Capacity ×2 區
對外流量(4 個 SKU)229.9 GiB 送出;流入 3.2 TB 不計費$0.08–0.12/GiB9/5 → 9/1224.80794帳單 SKU:Internet Data Transfer Out ×3、Carrier Peering ×1
Compute Engine 其餘 212 個 SKU外部 IP、其他區流量等—9/3 → 9/124.87156帳單依服務小計 143.32 減前 10 名 SKU
Vertex AI/Gemini(27 個 SKU)
原價 3.62,優惠折抵 0.83
212 次成功呼叫;文字+圖片輸入輸出依模型 token 計價9/7 → 9/122.7989帳單依 SKU 分組;Monitoring api/request_count 依 credential_id
Networking(負載平衡/位址)
原價 2.02,優惠全額折抵
——9/3 → 9/120.000帳單依服務分組
磁碟未刪前的後續費用
估;GCP 到 9/15 的預估總額為 192.84
600 GB pd-balanced≈ $0.10/GB·月9/15 起每月60.001,920牌價推算;VM 已 TERMINATED 但磁碟仍在
鑑識與申訴人力
估
約 1 人日顧問日費 NT$20k–35k9/13 → 9/156251094.00200,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.114,676
匯率假設 1 USD ≈ NT$32。單價為公開牌價或帳單實付,來源:GCP 帳單報表(依服務/依 SKU 分組)截圖 2026-09-15;Cloud Monitoring;Cloud Audit Logs。「舉證」欄標明每筆損失的證據來源與等級( 可證明 推論 缺口)。

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,則是在確認資產還活著。這是在經營,不是在搗亂。

lessons本案的 5 個教訓
  1. 01
    權限決定損害上限。攻擊鏈裡「提權」那一格是空的,不是因為我們擋住了,是因為一開始就給滿了。
  2. 02
    金鑰的壽命決定機會窗口。2020 年建立、沒有到期日的私鑰,2026 年一樣簽得出 token,而且不會有人收到通知。
  3. 03
    輪替的單位是身分,不是位置。平台清單只能說明那個平台現在存了什麼,repo 與本機不在它的視野裡。
  4. 04
    帳單是最便宜的一道偵測線。平常 $0 的專案 9/4 那天花了 $13,這條線不需要任何資安知識就看得懂。
  5. 05
    日誌的價值在事後。三種沒開的日誌,讓「資料有沒有被讀」這個客戶一定會問的問題,永遠只能回答「不知道」。

課堂討論題接在這五條後面,每題都沒有標準答案,目的是讓學員把結論搬到自己的環境:

  1. 8/27 收到平台通知後,我們依環境變數清單逐一輪替。為什麼這把 GCP 金鑰沒有被列進去?如果重來一次,清冊要怎麼建?
  2. 一個只讀寫試算表的 Bot 帳戶,為什麼會是 Owner?回想你負責的專案,有沒有「當初為了方便先給 Editor」的帳戶?
  3. 攻擊者第一個動作是找 AI 模型,不是開 VM。這對我們「哪些 API 應該開著」的判斷有什麼影響?
  4. 如果攻擊者開的是 GPU 機型,要多久會被 Google 停權?在那之前,我們的哪一個設定會先響?
  5. VM 從我們的專案收進 3.2 TB。如果下載的是盜版或違法內容,來源 IP 會追到我們。我們手上的證據足以證明不是自己下載的嗎?還缺什麼?

討論完,用下面十個關鍵字當作結業檢查:每一個都要能用自己的話講出「為什麼這篇非懂它不可」。

keywords重點學習:最需要學習的 10 個關鍵字10 個
  1. 01
    roles/owner
    一把只寫試算表的金鑰為什麼能開 VM、叫 AI:因為帳戶是 Owner。權限決定損害上限。
  2. 02
    Google Service KEY JSON
    長期有效的私鑰檔,複製到哪裡就在哪裡;外洩不是「會不會」,是「哪一份」。
  3. 03
    以身分為單位輪替
    照平台的環境變數清單輪替會漏掉 repo 裡的 KEY JSON;清冊要以「這個身分有幾份憑證」建。
  4. 04
    Workload Identity
    讓程式不需要持有私鑰就能叫 API,是「金鑰根本不存在」這道防線的實作。
  5. 05
    AcceptPublisherModelEula
    攻擊者第一個成功的動作:替你接受模型條款。這個方法名出現在 Bot 帳戶下就是警訊。
  6. 06
    instances.insert
    開 VM 的稽核事件名;一批多區同時、無服務帳戶、enable-oslogin=FALSE 是攻擊者的指紋。
  7. 07
    credential_id
    Monitoring 的 api/request_count 可依憑證分組;主控台看不到,這是唯一能看出「哪把金鑰在叫什麼」的方法。
  8. 08
    預算警示(budget alert)
    平常 $0 的專案一天花 $13,9/4 就該響。這是四道防線裡最便宜的一道。
  9. 09
    Admin Activity 400 天 vs 資料存取稽核
    前者預設開、保留 400 天,所以攻擊者每個動作都查得到;後者預設關,所以資料有沒有被讀永遠不知道。
  10. 10
    idle-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 的濫用偵測,而不是我們自己的任何一條線。下一次它會不會一樣準時,這題沒有答案;能決定的只有一件事——在它響之前,我們自己有沒有先響。

演練之前,先用下面五步唯讀檢查自己的環境,十五分鐘做得完。

try today今天就可以來試試約 15 分鐘

全部唯讀,不改任何設定;做完你會知道自己有沒有同樣的破口。

  1. 找出所有是 Owner 或 Editor 的服務帳戶
    gcloud asset search-all-iam-policies --scope=projects/PROJECT_ID \
      --query='policy:(roles/owner OR roles/editor) memberTypes:serviceAccount'
    看到什麼代表什麼:任何一筆都是紅燈。Bot 帳戶不該出現在這裡。
  2. 列出每個服務帳戶的使用者金鑰與建立日期
    gcloud iam service-accounts keys list --iam-account=SA_EMAIL --managed-by=user \
      --format='table(name,validAfterTime)'
    看到什麼代表什麼:validAfterTime 超過 90 天的,當成已外洩處理;這次那把是 6 年。
  3. 查 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 那一秒的畫面。
  4. 看帳單帳戶有沒有任何預算警示
    gcloud billing budgets list --billing-account=BILLING_ACCOUNT_ID
    看到什麼代表什麼:空的代表沒有人會在 9/4 收到通知。每個專案至少一條,門檻用平常費用的 2 倍;名字照本案取「budget-2x-f4k」,收到通知信一眼就知道是哪一條在響。
  5. 在 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 prompt把這篇的切入點帶回你的環境

複製下面這段,貼給你在用的 AI 工具(Claude Code、Cursor、Copilot、ChatGPT 都可以)。它會用本篇的破口位置、自我檢測與學習重點,對你的專案或環境做一次資安掃描,列出潛在問題與建議的調整或加強方式。

預設是唯讀:只發現問題、提出建議,不會改你任何檔案或設定。

§
對應課程模組

M2 身分與金鑰 · M3 雲端層偵測 · M0 駭客經濟學 · 想在自己的環境做一次同樣的盤點,從 自我盤點 開始。

quiz · 資安意識積分
讀完了?1 分鐘重點回饋,累積資安意識積分

4 題單選,答對每題 +10 分、全對再 +10 分。加入 LINE 官方帳號、在 LINE 裡作答,成績與積分直接存入;在網頁作答會拿到一組序號,傳到 LINE 就能累積。

1 分鐘重點回饋 →
newsletter
下一篇拆解直接寄到信箱

每篇新文章一封:三點摘要、關鍵字定義、今天能做的一件事。或先 加入會員 記錄你完成的任務。