TL; DR
Vint paquets npm publicats durant dos dies a finals de juliol i principis d'agost de 2026 es presenten com a biblioteques de components d'IU interns i utilitats de backend compartides. Cap d'ells implementa la funcionalitat que anuncia. Cadascun inclou una classe stub, un README que descriu el paquet com a intern i un fitxer anomenat _polyfill.js.
La part interessant és des d'on s'invoca aquest fitxer. No és un ganxo d'instal·lació — els manifests no declaren cap preinstal·lació, instal·lació o postinstal·lació. En canvi, l'última línia de index.js diu el següent: try { require("./_polyfill"); } catch (_) {}La càrrega útil s'executa la primera vegada que el codi de l'aplicació importa el paquet. Instal·lació amb --ignore-scripts, El standard consells per reduir l'exposició a la cadena de subministrament en el moment de la instal·lació, no ho impedeix.
Què _polyfill.js El que fa és recuperar un executable específic de la plataforma des d'un dels tres hosts de Cloudflare Workers, escriure'l a un directori temporal amb un nom aleatori, marcar-lo com a executable i iniciar-lo separat del procés principal. Aquest executable és en si mateix un carregador: enumera els productes de seguretat de punt final per nom de procés, comprova el nom d'usuari actual amb comptes d'entorn d'anàlisi coneguts, aplica pegats a les interfícies de telemetria AMSI i ETW a la memòria, instal·la la persistència amb el nom d'un component d'àudio de Windows i recupera un tercer binari de diversos megabytes de mida. La tercera etapa arriba xifrada i és desxifrat i executat a la memòria en lloc d'escrit al disc.
L'1 d'agost, aproximadament quatre hores després que es confirmés que els paquets eren maliciosos en una revisió manual, dinou dels vint noms van ser eliminats de npm pel seu propi editor en una seqüència de vint-i-cinc segons. El 2 d'agost, un d'aquests noms va ser registrat de nou, per un compte diferent, amb el mateix codi. La infraestructura de lliurament donava servei a les tres etapes quan es va escriure això.
| Paquets | 20 noms npm, ~74 versions publicades |
| Nomenament de temes | beaver-ui-* (13), accounts-* (3), a més afisha-storybook-default, bcore-bravo-eslint-config, arbocrate-sla-prober-arbocrate-sla-prober-core, a.poltoradnev-package-c |
| gallet | Importació de mòduls (require), no és un script del cicle de vida de npm |
| Etapa 1 | _polyfill.js — recuperació i llançament |
| Etapa 2 | Carregador enllaçat estàticament, empaquetat amb UPX a Linux; enumeració de seguretat de punt final, pegats AMSI/ETW, persistència |
| Etapa 3 | beacon_linux.bin / beacon_mac.bin / update_win.exe, 4–13 MB; lliurat xifrat, executat en memòria |
| Plataformes | Linux x64 i arm64, macOS universal, Windows x64 |
| Editors | Dos comptes, una base de codi |
| Estat | 19 de 20 noms no publicats per l'operador; 1 republicat i en directe; infraestructura de lliurament en directe |
Anatomia d'atac
Fase 1: el paquet
Cada membre envia el mateix formulari de tres fitxers. index.js exporta una classe amb un init () que estableix un booleà, un versió (), I un configurar() que fusiona un objecte d'opcions. Res més. LLEGIUME.md dóna una línia d'instal·lació, una exigir línia i la frase «Paquet intern. Vegeu CONTRIBUTING.md per obtenir directrius» — CONTRIBUTING.md no és al fitxer tar.
_polyfill.js té una mica menys de 5 KB i es llegeix com a codi d'infraestructura normal. S'obre amb tres comprovacions d'entorn:
if (process.env.DISABLE_TELEMETRY ||
process.env.ANALYTICS_OPT_OUT ||
process.env.DO_NOT_TRACK)
return; Aquestes variables són controls de privadesa convencionals. Aquí controlen si es descarrega i s'executa un binari. Una màquina que estableix NO_FER_SEGUIR s'omet completament.
A continuació ve un segell d'una sola execució: un fitxer a /tmp/.analytics_state, O estat_d'anàlisi sota % TEMP% a Windows, el temps de modificació del qual es compara amb una finestra de 20,357 segons. Dins d'aquesta finestra el mòdul retorna immediatament, de manera que les importacions repetides durant una compilació produeixen un intent de recuperació en lloc de desenes.
Els hosts de destinació no són presents com a literals. S'assemblen en temps d'execució a partir de fragments:
["oob-worker.cf102-","baf.workers.d","ev"].join("") Es construeixen tres amfitrions d'aquesta manera i es barregen abans d'utilitzar-los, de manera que l'ordre varia segons l'execució. La recuperació és un GET HTTPS fixat a IPv4 amb un Agent d'usuari of node-fetch/2.6 — un valor plausible perquè l'emeti un procés de Node. Una resposta inferior a 1,000 bytes es descarta i es prova el següent host.
Si els tres fallen, el mòdul torna a utilitzar el DNS. Emet una consulta TXT per a c. per obtenir un recompte de fragments, després sol·licita 0. a través de n. en lots de deu, concatena les respostes i descodifica el resultat en base64. Aquesta ruta porta el mateix executable sobre registres TXT DNS, que sobreviu en entorns on l'HTTP de sortida es filtra però la resolució DNS no.
Els bytes recuperats s'escriuen a /var/tmp/.cache_<8-hex> a Unix, o %TEMP%\dotnet_diag_<8-hex>.exe a Windows: un nom de fitxer que es llegeix com un artefacte de diagnòstic .NET. A Unix, el fitxer rep el mode 0755. L'inici és fresar amb separat: cert, stdio: "ignorar"i unref(), de manera que el fill sobreviu al procés Node que el va iniciar; a Windows la invocació afegeix finestresAmaga: cert.
Dos detalls completen la imatge. Un ajudant definit com a const _l = (…a) => process.stderr.write(“”) es crida a cada pas de la recuperació: escriu una cadena buida i descarta els seus arguments, ocupant la posició on es trobaria el registre. I cada paquet envia un fitxer de 81 KB a lib/telemetria.js el comentari de la capçalera del qual descriu un "SDK d'Analytics" amb "transport HTTPS amb rotació de punt final i reintent" i "descobriment de serveis basat en DNS per a failover". A la compilació actual no hi ha res que ho requereixi.
Etapa 2: el carregador
El binari de Linux x64 és un ELF enllaçat estàticament comprimit amb UPX 3.96. La compilació de macOS és un binari universal que cobreix x86_64 i arm64; la compilació de Windows és un executable de consola PE32+ despullat. Tots quatre es van recuperar i examinar estàticament; no se'n va executar cap.
Les importacions i la taula de cadenes de la compilació de Windows descriuen clarament el seu comportament. Resol IsDebuggerPresent, ComprovaDepuradorRemotPresenti Procés d'informació de consulta Nt, i llegeix SISTEMA\CurrentControlSet\Serveis\Disc\Enum — una ruta de registre els valors de la qual identifiquen controladors de disc virtuals. Crida ObténEspaiLliureDeDiscExW i ExEstatMemòriaGlobal, d'acord amb la comprovació de si els recursos de l'amfitrió coincideixen amb una instància d'anàlisi de curta durada.
Conté una llista de noms d'usuari: Usuari actual, Administrador de TI, Hong Lee, CAIXA DE PROVA, hapubws, milozs, tímid, admin, el malware, mostra, virus, medi ambientDiversos d'aquests són artefactes d'entorns d'anàlisi documentats públicament.
També conté una llista de productes de seguretat per a endpoints i els seus noms de processos, enumerats mitjançant CreateToolhelp32Snapshot i Process32FirstW/Process32NextW:
- CrowdStrike Falcon (
csfalconservice,CSFalconContainer) - Carbó negre (
cb.exe) - Cylance (
cylancesvc.exe) - Bitdefender (
bdagent.exe) - Kaspersky (
avp.exe,kavfs.exe) - Trend Micro i Deep Security (
coreServiceShell,ds_agent.exe) - FireEye/Trellix (
xagt.exe) - Tani (
taniumclient.exe) - HitmanPro (
hmpalert.exe) - Elàstic (
elastic-agent.exe,elastic-endpoint,filebeat.exe,winlogbeat.exe) - Wazuh i OSSEC (
wazuh-agent.exe,ossec-agent.exe) - Check Point
- Windows Defender
Hi ha dos símbols més presents: AmsiScanBuffer, el punt d'entrada que Windows exposa per a l'escaneig de contingut en memòria i EtwEsdevenimentEscriptura, la funció a través de la qual el seguiment d'esdeveniments per a Windows registra els esdeveniments. Ambdues són objectius comuns per a la modificació en procés que suprimeix la telemetria que aquestes interfícies produirien d'altra manera.
La persistència apareix dues vegades a Windows. El binari es copia a %APPDATA%\Microsoft\Windows\Themes\SystemSoundsService.exe, i dos mecanismes hi fan referència: un valor sota PROGRAMARI\Microsoft\Windows\Versió actual\Executar, i una tasca programada registrada com a
schtasks /Create /F /SC ONLOGON
/TN "\Microsoft\Windows\Multimedia\SystemSoundsService"
/TR "\"%s\""
/RL HIGHEST La ruta de la tasca, el nom de la tasca i el nom de l'executable reflecteixen un component d'àudio genuí de Windows. /RL MÉS ALT sol·licita el nivell de privilegis més alt disponible.
La compilació de macOS segueix el mateix patró amb noms apropiats per a la plataforma: escriu una llista de propietats per a ~/Library/LaunchAgents/com.apple.windowserver.helper.plist i col·loca el seu executable a ~/.local/share/runtime/com.apple.runtimeComprova si /Aplicacions/VMware Fusion.app i /Biblioteca/Suport d'aplicacions/Eines de VMware.
La capacitat de la xarxa abasta tres transports: WININET.dll per HTTPS amb un https://%s:%d%s cadena de format, WS2_32.dll endolls i DNSAPI.dll amb DnsQuery_A al costat d’un c.%s.%s format string — la mateixa forma de consulta de recompte de fragments que utilitza la primera etapa.
La descripció anterior és el que mostren les importacions, les cadenes i les rutes de registre del binari en inspeccionar-lo. L'anàlisi pública multimotor del mateix exemple de Windows afegeix detalls que una lectura estàtica d'una taula d'importació no pot establir per si sola, i s'atribueix aquí en lloc de reformular-se com a observació de primera mà. Aquesta anàlisi informa que les referències AMSI i ETW no només estan resoltes sinó que també s'han corregit. AmsiScanBuffer in amsi.dll i EtwEsdevenimentEscriptura in ntdll.dll — que suprimeix l'escaneig de contingut en memòria i el seguiment d'esdeveniments per al procés. Informa de les comprovacions antianàlisi com a basades en el temps, a més de ComprovaDepuradorRemotPresent, i identifica el ObtenirContextDeFil crida com a comprovació de punt d'interrupció de maquinari que llegeix els registres de depuració. També informa d'un mutex d'una sola instància, Global\{7A3B9E1F-C2D4-4F8A-B5E6-D7A1F0C3E2B9}, i noms Servei de sons del sistema com a valor de la clau d'execució. Aquest valor de mutex no es trobava entre les cadenes recuperades aquí (el filtre d'extracció utilitzat excloïa els tokens hexadecimals majúscules entre parèntesis) i es registra a la taula d'indicadors basant-se en aquesta anàlisi pública en lloc de l'observació directa.
Etapa 3
El carregador fa referència a un conjunt addicional de camins als mateixos hosts. Tots eren accessibles durant l'anàlisi: beacon_linux.bin a 5.3 MB, beacon_mac.bin a 13.3 MB, actualització_win.exe a 10.3 MB, i arm.bin a 4.3 MB. No s'han recuperat i se'n desconeix el comportament.
Per a la ruta de Windows, l'anàlisi pública del carregador descriu com es gestiona aquesta tercera etapa: s'obté a través de WinINet, arriba xifrada i es desxifra a la memòria i s'executa de manera reflexiva: memòria assignada, importacions resoltes, reubicacions processades i control passat a un nou fil mitjançant Crear filPer aquest motiu, la tercera etapa no s'escriu mai al sistema de fitxers, de manera que la detecció basada en fitxers i l'anàlisi forense de disc posterior a incidents no tenen res a recuperar. També significa que recuperar els bytes de l'amfitrió de lliurament generaria text xifrat, no un executable analitzable.
Línia de temps
Totes les marques de temps són UTC i provenen de les metadades del registre npm, que registren la creació i la despublicació de paquets independentment de qualsevol observador.
| Quan? | Què |
|---|---|
| 2026-07-31 09:25:05 → 09:29:56 | Deu noms creats, cadascun començant per la versió 0.0.1, amb uns tretze segons de diferència. |
| 2026-08-01 09:38 → 11:29 | Els noms existents pugen a través de números de versió inflats, 6.1.9 a través de 6.1.12, 9.9.9 a través de 9.9.12, 8.9.3 a través de 8.9.6, 5.4.6 a través de 5.4.9. |
| 2026-08-01 13:04:14 → 13:04:18 | Nou més beaver-ui-* noms creats en una ràfega de quatre segons, cadascun publicat directament en una versió 12.x sense historial previ. |
| 2026-08-01 ~13:00–14:30 | Versions classificades com a malicioses en la revisió manual. |
| 2026-08-01 17:39:49 → 17:40:14 | Dinou dels vint noms inèdits en vint-i-cinc segons, seqüencialment, amb una diferència d'aproximadament 1.2 segons. |
| 2026-08-02 18:58:41 | a.poltoradnev-package-c creat de nou amb un compte de mantenidor diferent, versió 0.0.2. |
| 2026-08-02 19:20:28 | version 33.9.5 publicat amb el mateix nom. |
| 2026-08-02 20:14 | Els tres amfitrions de lliurament serveixen les etapes dos i tres; la resposta alternativa del DNS amb recomptes de blocs. |
Dues d'aquestes entrades mereixen una atenció especial.
La ràfega de les 13:04 va posar nou paquets al registre en quatre segons, cadascun amb una sola versió en els dotze i cap 0.0.1 a sota. Els números de versió en aquesta escala, en noms d'un dia d'antiguitat, no descriuen un historial de llançaments.
La seqüència de despublicació és igualment mecànica: dinou noms, vint-i-cinc segons, espais uniformes. npm registra aquests com a esdeveniments de despublicació ordinaris en lloc del marcador de posició que deixa una eliminació iniciada pel registre, cosa que situa l'acció amb el compte de publicació en lloc del registre.
Indicadors de compromís
Els hosts i les rutes estan desactivats. No els resolgueu ni els recupereu d'un sistema de producció.
| Tipus | Valor |
|---|---|
| Amfitrió de lliurament | oob-worker[.]cf99-9b3[.]workers[.]dev |
| Amfitrió de lliurament | oob-worker[.]cf102-baf[.]workers[.]dev |
| Amfitrió de lliurament | oob-worker[.]cf103-070[.]workers[.]dev |
| Transport DNS | tin[.]dl[.]well1[.]site (Linux x64), tina[.] (arm64), ldr[.] (macOS), win[.] (Windows) — recompte de fragments a c.<domain>, dades a <n>.<domain> |
| Camins de la fase 2 | /pkg/package, /pkg/package-arm64, /pkg/loader_mac, /pkg/package.exe |
| Camins de la fase 3 | /pkg/beacon_linux.bin, /pkg/beacon_mac.bin, /pkg/update_win.exe, /pkg/arm.bin |
| Camí de descàrrega (Unix) | /var/tmp/.cache_<8-hex> |
| Camí de desplegament (Windows) | %TEMP%\dotnet_diag_<8-hex>.exe |
| Persistència (Windows) | %APPDATA%\Microsoft\Windows\Themes\SystemSoundsService.exetasca \Microsoft\Windows\Multimedia\SystemSoundsServiceValor de la clau d'execució SOFTWARE\Microsoft\Windows\CurrentVersion\Run\SystemSoundsService |
| Mutex (Windows) | Global\{7A3B9E1F-C2D4-4F8A-B5E6-D7A1F0C3E2B9} |
| Persistència (macOS) | ~/Library/LaunchAgents/com.apple.windowserver.helper.plist; binari a ~/.local/share/runtime/com.apple.runtime |
| Segell d'una sola execució | /tmp/.analytics_state, %TEMP%\analytics_state (finestra de 20,357 s) |
| Sol·licitar signatura | HTTPS GET, IPv4-pinned, User-Agent: node-fetch/2.6 |
| Fitxer de la fase 1 | _polyfill.js, SHA-256 392dc9ec85042ce1796bfa9b8c7bf6e05c7710579aab8b7b1eb73788683883af |
| Fitxer sense referència | lib/telemetry.js, SHA-256 2266c9b8e199daebe31a9afcb61d9b0d76706b63c92312fc8223d22de42e63c7 |
| Etapa 2 (Linux x64) | SHA-256 7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3 |
| Etapa 2 (Linux arm64) | SHA-256 aafc37589c0d21fb729761a1579ca3b278b95bce152aacfa29243ffd27f5eeb7 |
| Etapa 2 (macOS) | SHA-256 c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00 |
| Etapa 2 (Windows) | SHA-256 d632c2c0c7801c3da57cf849fe4b4ca8351d9a7fe73c31050bf5dbcc96a16791 |
Noms de paquets
Atribució i comportament observat
Hi ha dos comptes npm implicats. Els dinou noms no publicats van ser publicats per un compte; a.poltoradnev-package-c es va tornar a registrar el 2 d'agost amb un nom de compte diferent i un domini de correu electrònic diferent. El codi és el mateix llinatge en tots dos casos, i el segon registre porta els mateixos amfitrions de lliurament, els mateixos dominis DNS alternatius, la mateixa construcció de ruta de sortida i el mateix segell d'execució única.
El parell reeditat també mostra com es va muntar. Versions 0.0.2 i 33.9.5 són idèntics en bytes tret de la cadena de versió a paquet.json, I el index.js enviat com a 0.0.2 declara const VERSIÓ = “33.9.5”Tots dos artefactes es van tallar d'un arbre font i es van numerar posteriorment. No hi ha cap 0.0.2 història del desenvolupament per a la 0.0.2 alliberament per representar.
La numeració de versions a tot el conjunt segueix la mateixa lògica. Els noms de paquets en aquesta campanya es llegeixen com a coses que existirien dins d'una organització en lloc de ser en un registre públic: un conjunt de components d'IU, una configuració ESLint, un sondeig SLA, un paquet que porta el nom d'una persona. Quan una compilació resol una dependència per nom i consulta un registre públic juntament amb un de privat, el número de versió més alt és el que guanya. Un nom creat a 0.0.1 dilluns i portant 12.9.6 dimarts està posicionat per a aquesta comparació.
Les dues onades de publicació també difereixen d'una manera que suggereix iteració. El codi anterior anomenava el seu fitxer de càrrega útil setup.js i va enumerar cinc amfitrions de lliurament; el fitxer actual s'anomena _polyfill.js i enumera tres. La versió anterior eliminava el fitxer eliminat cinc segons després de l'inici, deixant el procés executant-se des d'un inode eliminat; la versió actual no ho fa. El fitxer sense referència lib/telemetria.js roman al tarball d'ambdós, encara enviat, ja no està connectat.
Val la pena esmentar prèviament una observació sobre els interruptors de apagada.cisely, perquè va en contra d'una lectura comuna. DISABLE_TELEMETRY, ANALYTICS_OPT_OUTi NO_FER_SEGUIR es respecten i es comproven abans que passi res més. Un investigador l'entorn d'anàlisi del qual estableix qualsevol d'elles observa un paquet que no fa res en absolut.
Impacte i què poden fer els defensors
La conseqüència pràctica del desencadenant en el moment de la importació és limitada i val la pena esmentar-la per si sola. instal·lació de npm –ignore-scripts impedeix que s'executin scripts del cicle de vida, i és un bon consell. No té cap efecte aquí, perquè aquest codi no utilitza un script del cicle de vida. La càrrega útil s'executa quan el codi de l'aplicació, una execució de prova, un agrupador o un linter importen primer el mòdul. En un executor de CI que s'instal·la i després es compila immediatament, els dos estan separats per segons.
Per a la caça, els senyals duradors són estructurals més que no pas literals. Els hosts, el nom del fitxer i les rutes de solta canviaran; aquestes formes són més difícils de variar:
- Un punt d'entrada de mòdul l'última instrucció del qual és un prova { requereix("./x"); } catch (_) {} al voltant d'un fitxer que no forma part de l'API exportada. Un paquet amb un polyfill genuí l'importa a la part superior i utilitza el seu resultat. Empassar-se tots els errors d'una importació al final del fitxer no és gestió d'errors.
- Cadenes d'amfitrió muntades per .uneix-te ("") d'una sèrie de fragments. El codi legítim no té cap raó per dividir un nom d'amfitrió entre elements de matriu.
- Un paquet les dependències declarades del qual estan buides però que arriba a https, dnsi procés_fill en el moment de la importació. La recuperació més l'execució en un paquet que anuncia un component d'IU és tot el descobriment.
- Números de versió inconsistents amb l'antiguitat d'un paquet. Un nom publicat per primera vegada fa dies en una versió principal de dos dígits, sense llançaments intermedis, descriu alguna cosa més que el seu desenvolupament.
- Escriu a / Var / tmp or % TEMP% seguit d’un Chmod a 0755 i una generació separada. La seqüència és curta, ordenada i rarament benigna junta.
En els amfitrions Windows hi ha dues comprovacions barates i d'alta confiança. Els artefactes de persistència són cadenes fixes i cap d'elles pertany a un sistema net: una tasca programada a \Microsoft\Windows\Multimèdia\Servei de sons del sistema, un valor de clau d'execució anomenat Servei de sons del sistema, i un executable amb aquest nom sota %APPDATA%\Microsoft\Windows\Temes\El servei d'àudio genuí de Windows no es troba en cap d'aquests llocs. En segon lloc, el carregador utilitza un mutex d'instància única fix, Global\{7A3B9E1F-C2D4-4F8A-B5E6-D7A1F0C3E2B9}; la seva presència en un host és un indicador, i com que el carregador tracta un mutex existent com a "ja en execució", crear-lo prèviament és una inoculació viable a curt termini mentre es programa la remediació.
La detecció a la tercera etapa és un problema més difícil i val la pena aclarir-ho. Segons l'anàlisi pública, es desxifra i es mapeja a la memòria en lloc d'escriure-la al disc, de manera que l'escaneig de fitxers i l'anàlisi forense del disc no la veuran. El que queda observable és la pròpia petjada del carregador: les entrades de persistència, el mutex i les sol·licituds de sortida als hosts de lliurament o als dominis DNS-TXT.
Per a entorns que resolen dependències tant d'un registre privat com d'un de públic, la mitigació no canvia per res d'això: abastar els noms interns de manera que un nom públic no els pugui satisfer i fixar la resolució al registre intern per a aquests àmbits. El comportament d'inflació de versions descrit anteriorment només funciona quan una versió pública superior pot obtenir un nom.
Finalment, val la pena llegir atentament la despublicació en lloc de com un final. Dinou noms van abandonar el registre en vint-i-cinc segons i un va tornar l'endemà amb un compte nou, amb la infraestructura de lliurament intacta en tot moment. L'eliminació de paquets no és l'eliminació de l'operació; els amfitrions van continuar servint cada etapa mentre s'escrivia això. Qualsevol arbre de dependències que resolgués un d'aquests noms entre el 31 de juliol i l'1 d'agost s'hauria de considerar que ha tingut l'oportunitat d'executar la primera etapa, tant si el nom encara existeix com si no.
referències
- Anàlisi multimotor del carregador de Windows — font per a l'aplicació de pegats AMSI/ETW, l'execució reflexiva en memòria i els detalls del mutex atribuïts anteriorment.
- documentació de scripts npm — els esdeveniments del cicle de vida que aquesta campanya no utilitza.
- documentació d'instal·lació de npm — cobertes -ignore-scripts i el seu abast.




