TL博士
一群 五個 npm 包透過兩個帳戶帳號發布,已交付 postinstall 一個鉤子程序,用於從主機讀取雲端憑證並將其發送到外部伺服器。這些軟體包使用類似幽靈或海盜的名稱—— coral-wraith, ecto-corsair-whisper-6f3b9, ecto-corsair-flag-x9m4, ecto-rust-read-f3a9c1, ecto-nightly-spirit ——並將他們收集到的任何東西序列化成假貨 ecto_module: 在傳輸之前,我們會追蹤集群。 外質.
有效載荷僅在偵測到特定環境時運行:即主機名稱為 a 的主機。 12 個字元的十六進位字串 以及一個位於以下位置的工作目錄 /app/node_modules —容器化建置或CI工作流程的形狀。當該門通過時,鉤子會查詢 AWS實例元資料服務(IMDSv2) 對於 IAM 角色憑證,枚舉 AWS機密管理器 跨越三個區域,轉儲環境變量,讀取以下目錄下的文件 /app並抓取奪旗賽字串。然後,它透過兩種方式洩漏結果:向…發送信標。 webhook.site 收集器和清單 PUT 到原始 IP 端點,並包含 localhost 優先回退清單。
後期的軟體包描述為「用於 verdaccio 供應鏈測試的 CTF 有效載荷」。我們報告此描述為可觀察的事實。其行為本身——即時向公用 IP 位址發出出口流量、讀取真實的 IMDS 憑證、呼叫真實的 Secrets Manager——與標籤無關,也正是這些版本被判定為惡意的原因。
叢集中的一個名稱, coral-wraith但它並沒有止步於一次發布。在短短幾個小時內,它迅速推出了數十個版本—— 1.0.0 攀登至 6.0.0 ——而之前同名的版本曾使用過膨脹 9999.0.x 版本號,經典形狀 依賴關係混淆嘗試在這過程中,有效載荷明顯成熟了:從一次性的主機枚舉信標發展成為完整的 AWS 憑證轉移,並包裹在環境檢查中,使其在目標之外保持靜默。
| 配套 | 5個名字; coral-wraith 光是這一點就被重新出版了數十個版本 |
| 生態系統 | NPM |
| 安裝向量 | postinstall 生命週期腳本 |
| 主要目標 | AWS IAM 角色憑證 + Secrets Manager 金鑰值、環境變數 /app 檔 |
| 外洩 | webhook.site 信標 + 原始 IP C2 投遞 |
| 觸發門 | 12 位元十六進位主機名稱 + /app/node_modules 當前工作目錄,以及一個環境檢查,用於抑制該上下文之外的有效載荷。 |
| 嚴重性 | 高 —雲端憑證和託管金鑰洩露 容器化建置與運行時環境 |
攻擊解剖
叢集中的每個軟體包都是以相同的方式建構的:一個幾乎為空的軟體包 index.js (module.exports = {}),一行 的package.json 腳本 - “postinstall”: “node postinstall.js” ——以及酬載 postinstall.js安裝該軟體包即可運行鉤子;無需匯入或呼叫。
目標鎖定門。 在執行任何操作之前,外骨骼家族的有效載荷會檢查周圍環境:
function isAppWorker(): host = os.hostname() if host does NOT match /^[0-9a-f]{12}$/ -> exit if cwd does NOT contain "/app/node_modules" -> exit if cwd contains "/tmp/npm-safe" -> exit otherwise -> proceed Docker 預設指派給容器的主機名稱格式為 12 位元十六進位。 /app/node_modules 這是一個傳統的容器內安裝路徑。如果路徑看起來像一個沙盒化的提取目錄,則第三條規則會失效。最終結果是,有效載荷會保持在開發人員筆記型電腦或分析沙箱中處於休眠狀態,僅在容器化的建置或運行時工作環境中啟動——這種環境最有可能承載即時雲端憑證。集群中最早的軟體包, 珊瑚幽靈沒有這樣的門,並且無條件地運行其(更簡單的)集合。
保養系列當閘門通過時,鉤子會從閘門伸出。 執行檔同步(“/bin/sh”,[“-c”,...]) 並運行一個複合命令,該命令按以下順序執行:
1. PUT /latest/api/token to 169.254.169.254 (IMDSv2 token request) 2. GET .../iam/security-credentials/ (IAM role name) 3. GET .../iam/security-credentials/<role> (temporary credentials) 4. dump env | sort (environment variables) 5. list /app (excl. node_modules) + cat first 15 (application files) 6. aws secretsmanager list-secrets (us-east-1, eu-west-1, eu-central-1) 7. scrape readable files for HTB{...} (capture-the-flag strings) 步驟 1-3 是標準的 IMDSv2 檢索流程:請求會話令牌,然後將其作為附件附加。 X-aws-ec2-metadata-token 請求頭用於取得實例的 IAM 角色及其臨時存取金鑰。選擇實作 IMDSv2 而不是更簡單的無需身份驗證的 IMDSv1。 GET 值得注意的是,這意味著即使在配置為需要基於令牌的元資料存取的實例上,有效載荷也能正常運作,而這正是 AWS 推薦的安全加固措施。步驟 3 傳回的憑證是短期有效的。 存取密鑰 ID/秘密存取密鑰/Token 三元組的作用域限定於實例的角色;該角色可以執行的任何操作,持有這些金鑰的人都可以在憑證的生命週期內執行。
步驟 4-6 擴大了採摘範圍。 ENV dump 檔案會捕獲建置或執行時間流程繼承的所有內容——實際上,註冊表令牌、資料庫連接字串和 API 金鑰通常都儲存在這裡。 /應用 文件遍歷最多可以讀取外部的十五個應用程式檔案。 node_modules它可以顯示表面配置, .ENV 文件或來源。步驟 6 調用 aws secretsmanager list-secrets 在三個區域中;步驟 1-3 中提取的憑證正是用來驗證這些呼叫的憑證,因此 IMDS 讀取和 Secrets Manager 枚舉鏈結合成單一的升級過程:實例角色 → 託管金鑰清單。步驟 7 是對奪旗模式的致敬—當 HTB{…} 如果找到標誌,則單獨發送;否則,收集到的原始資料塊將被分成四塊發送。
叢集中的後續版本進一步升級了功能。它們不僅完成清單清點,還會解析 IMDS 回應,並將臨時金鑰匯出為 AWS存取金鑰ID / AWS 秘密存取金鑰 / AWS_SESSION_TOKEN 環境變量,確認身份 aws sts 獲取來電者身份然後遍歷回傳的每個密鑰 秘密列表 調用 aws secretsmanager get-secret-value 在每個程式中-檢索秘密內容,而不僅僅是它們的名稱。相同的版本還可以讀取進程替換標誌二進位(/readflag 和朋友)並嘗試 充電運行 針對任何在以下位置找到的 Rust 項目 /應用將收穫範圍從雲端憑證擴大到建構環境暴露的所有內容。
這些後續版本也採取了更嚴格的自我限制措施。除了 12 位元十六進位主機名稱之外, /app/node_modules 此有效載荷會檢查活動軟體包註冊表配置和工作目錄路徑,如果它們指示的是分析或鏡像上下文而非即時目標,則會靜默退出。綜合來看,此有效載荷在大多數檢測環境中不會執行任何可觀察到的操作,並且僅在判斷自身位於真正的容器化主機上時才會運行完整的收集過程。
滲出收集到的資料會經由兩個通道離開主機。首先是信標通道。 解決方案&帖子 固定 webhook.site 收集器攜帶主機名稱、數位 UID、工作目錄以及最多 120 KB 的收集資料。其次,數據被折疊成一個偽造的 YAML「模組清單」並 PUT 至 /api/modules/ 在目標伺服器上:
ecto_module: name: "<flag-or-chunk-0>" version: "1.0.0" power_level: "<chunk-1>" ship_deck: "<chunk-2>" cargo_hold: "<chunk-3>" 清單欄位名稱(功率等級, 船甲板, 貨艙)只是裝飾——被盜資料隱藏在字串值中,這就是為什麼網路監視器看到的是看似無害的軟體包註冊表清單上傳,而不是明顯的資料轉儲。信標通道承載更多資訊: 解決方案&帖子 身體到 webhook.site 包括主機名稱、數位 UID、工作目錄以及最多 120 KB 的收集到的資料區塊,因此即使只有一個成功的信標也能提供完整的資料。 webhook.site 是一項免費的請求檢查服務;將其用作收集器意味著運營商永遠不必為該通道建立自己的接收基礎設施,並且記錄的請求會保留在該服務的存儲庫中。
清單 PUT 遍歷一個以多個開頭的備用列表 127.0.0.1/本地 端口,然後落入三個公共位址154.57.164.0/24` 範圍,直到第一個回應 2xx 狀態碼的端點為止。本機優先的順序與「verdaccio 測試」的自我描述(環回位址上的本機註冊表)一致,但公網 IP 回退機制意味著,只要環回位址不監聽,資料就會離開主機——也就是說,在任何非作者自己的測試機器上都會發生這種情況。
時間線
該集群展現出能力的逐步成長,而非一次性下降。我們按觀察到的行為而非發佈時間對其進行排序:
| 階段 | 軟體包/版本 | 行為 |
|---|---|---|
| 早期運行 | coral-wraith 9999.0.x | 版本號虛高,疑似試圖混淆依賴關係;安裝時枚舉和資料外洩 |
| 種子 | coral-wraith 1.0.0 | 安裝後收集 id/env/flag 檔案;單一 PUT 請求 154[.]57[.]164[.]71:30782標記 ECT-472839 |
| 快速迭代 | coral-wraith 1.0.1 → 6.0.0 | 數小時內進行了數十次釋放;有效載荷增加 isAppWorker() 門禁、IMDSv2憑證擷取、完整 get-secret-value 樞軸、註冊表/路徑環境檢查和雙接收器標記 |
| 平行名稱 | ecto-corsair-whisper-6f3b9 1.0.14–1.0.18 | 相同的門控有效載荷; webhook.site 信標和多端點回退列表 |
| 變體 | ecto-rust-read-f3a9c1 1.0.1–1.0.2 | 增加額外的水槽標記 ECT-987654, ECT-654321, ECT-839201 |
| 變體 | ecto-corsair-flag-x9m4 1.0.0, ecto-nightly-spirit 1.1.0 | 相同的門控有效載荷、相同的C2和信標 |
該集群最顯著的特點是其發布節奏:並非只有一個軟體包和一個版本,而是同一個名稱快速地反覆發布,每個版本都只是前一個版本的微小變化,同時還有幾個名稱不同的同級軟體包攜帶相同的有效載荷。 耳語 這個家族程式碼分裂成兩個非常接近的指紋——一組觸發兩個關鍵檢測,另一組觸發三個(一個額外的檔案讀取接收器)——但兩者都解析到相同的有效載荷;區別在於程式碼漂移,而不是行為上的分支。版本 耳語 超出分析範圍(截至撰寫本文時至少達到 1.0.25)的序列已在註冊庫中即時觀測到,並且 珊瑚幽靈 名稱繼續沿著同一視窗向上攀升。
妥協指標
以下所有指標均提取自磁碟上的軟體包原始碼。網路指標已進行去重處理。
網絡
| 指標 | 職位 |
|---|---|
hxxp://154[.]57[.]164[.]71:30782 | C2 PUT 目標(coral-wraith) |
hxxp://154[.]57[.]164[.]80:30543 | C2 PUT 回退(ecto-*) |
hxxp://154[.]57[.]164[.]82:31250 | C2 PUT 回退(ecto-*) |
hxxp://154[.]57[.]164[.]71:31289 | C2 PUT 回退(ecto-*) |
hxxps://webhook[.]site/602a4c72-7033-4e28-92ea-dc66e59206e5 | 信標收集器 |
169[.]254[.]169[.]254/latest/... | IMDSv2 憑證讀取(目標端,AWS 元資料) |
行為/文件
| 指標 | 職位 |
|---|---|
"postinstall": "node postinstall.js" | 安裝向量 |
ecto_module: YAML power_level / ship_deck / cargo_hold 鍵 | 撤離清單方案 |
水槽標記 ECT-472839, ECT-987654, ECT-654321, ECT-839201 | C2路徑段 /api/modules/<marker> |
isAppWorker() 門:主機 /^[0-9a-f]{12}$/,cwd 包含 /app/node_modules | 啟動條件 |
aws secretsmanager list-secrets 超过 us-east-1, eu-west-1, eu-central-1 | 秘密枚舉 |
HTB{...} 正規表示式抓取 | 奪旗收割 |
文件哈希值(sha256,分析時捕獲)
| 文件 | sha256 |
|---|---|
coral-wraith/postinstall.js | ce5ff035cfdfed1d0015446424b352c27b66bcb77e9fdb0a51e4245199146824 |
ecto-corsair-whisper-6f3b9 1.0.18/postinstall.js | b58432acba376aa6976f0490d9a1c04257ccdbc856d8390260c50322d63e31c3 |
歸因與觀察行為
這五個軟體包名稱分別發佈在兩個不同的 npm 帳戶下,但它們共享足夠的基礎設施,可以將它們視為一個集群:相同的 ecto_module 清單模式相同 ECT-472839 主要匯水標誌,相同 webhook.site 收集器 ID 和 C2 端點位於相同位置 154.57.164.0/24` 區塊。種子包(`coral-wraith`)(更簡單且無限制)因此,受限的 ecto-family 可以理解為對同一工具包的迭代,而不是獨立努力。
這些軟體包在後續版本中將自身描述為: “用於 verdaccio 供應鏈測試的 CTF 有效載荷。” 我們將該標籤作為可觀察的事實呈現,而非將其重新表述為關於目的的結論。程式碼的功能明確無誤,與其標籤無關:它從實例元資料服務讀取 IAM 角色憑證,列舉跨三個 AWS 區域的託管金鑰,並將結果傳輸到公有 IP 位址和第三方 Webhook 收集器。一個真正僅使用環回的測試框架不需要公有 IP 位址回退清單、IMDS 憑證讀取或跨區域的 Secrets Manager 呼叫。由於出口流量和憑證存取是真實的,因此受限制的版本被判定為惡意版本。
容器內存取限制是此機制在操作層面上最顯著的特徵。它既是一種規避措施(在筆記型電腦和分析沙箱中保持靜默),也是一種目標定位措施,僅在最有可能存在真實身份與訪問管理 (IAM) 角色和有效密鑰的地方才會觸發。在通用沙箱中執行這些軟體套件的分析人員不會觀察到任何異常;該機制僅在使用 Docker 風格的主機名稱和容器內安裝路徑時才會顯現。
影響、趨勢及對防守者的指導
這裡存在的風險是雲端憑證和密鑰在建置和運行時容器內部洩漏。從 IMDS 取得的 IAM 角色憑證包含該角色擁有的所有權限; secretsmanager:列出秘密 (以及任何後續) 取得秘密值這會將洩漏範圍擴大到儲存的應用程式密鑰。環境變數轉儲通常包含註冊表令牌、資料庫 URL 和 API 金鑰。在持續整合 (CI) 或容器環境中(這正是該安全門所針對的),只需傳遞安裝其中任何一個軟體包,就足以洩露這些資訊。
Ectoplasm 符合我們持續觀察到的一種模式:安裝時有效載荷會獲取雲端元資料和託管金鑰,而不是本地文件,並且會限制自身僅在高價值環境中觸發。以下是兩點防禦性觀察。
- 此形狀可檢測。一個 npm/PyPI 安裝鉤子,其呼叫圖同時連接到雲端金鑰 API(AWS SecretsManager, gcloud 秘密, az keyvault或者 IMDS 位址和網路出口接收器是一種狹窄的高訊號模式——它幾乎不會出現在合法的生命週期腳本中。 靜態流分析 無需依賴任何特定網域名稱或 IP 位址即可標記出來。
- 環境硬化會削弱它的作用。 強制執行 IMDSv2 並將跳數限制設為 1 可以防止容器工作負載存取實例元資料;將 IAM 角色權限範圍限定為最小權限可以限制任何外洩憑證的影響範圍;以及執行安裝程式時使用 –忽略腳本 CI 會完全移除不需要安裝鉤向量的軟體包的安裝鉤向量。
對於防禦者而言,實際的檢查措施包括:在建置/CI 容器與非允許清單中的公用 IP 位址建立出站連線時發出警報。 npm安裝; 注意來自軟體包生命週期腳本的 IMDS 存取;並將任何呼叫雲端 CLI 的安裝鉤子視為可疑,直到證明並非如此為止。
針對此集群還有兩點要注意。首先,由於啟動僅限於容器化環境,因此在工作站上驗證時看似無效的軟體套件在生產環境中仍可能處於活動狀態——驗證需要重現容器的主機名稱和路徑條件,或者直接讀取原始程式碼,而不能僅僅依賴「我安裝了它,但什麼也沒發生」這種說法。其次,使用公共請求檢查服務作為信標收集器意味著部分洩露的資料可能可以恢復用於事件回應:如果組織在其依賴樹中發現此類軟體包,則可以根據有效負載的收集邏輯推斷成功的信標會包含哪些內容,並應輪換任何可從受影響的建置或運行時環境存取的 IAM 角色憑證、註冊表令牌和託管令牌。 資格輪換安裝完成後,有效的補救措施是刪除軟體包,而不是刪除軟體包。





