Cargo, narzędzie do pobierania cudzych bibliotek w języku Rust, ma uprzejmy zwyczaj. Kiedy projekt korzysta z wersji, którą autor oznaczył jako wycofaną, przy budowaniu pojawia się podpowiedź, żeby rozważyć przejście na nowszą. W czwartek rano przez półtorej godziny ta podpowiedź prowadziła prosto na złośliwy kod. Wystarczyła jedna kompilacja, żeby ściągnął on obcy program i uruchomił go w tle. Ktoś przejął konto autora biblioteki arrayref, oznaczył jako wycofane wszystkie wydania od 0.3.5 do 0.3.9 i wypuścił jedno własne. Używane w praktyce wersje świeciły więc ostrzeżeniem, a czysta z pozoru była ta podstawiona. Programista, który zgłosił sprawę publicznie, napisał wprost, że sam się na to nabrał.
Fałszywy pakiet podszył się pod bibliotekę do makr
Jednym z elementów ataku na łańcuch dostaw była różnica jednej cyfry w nazwie. W ekosystemie Rusta od lat istnieje proc-macro2, biblioteka z blisko 1,49 miliarda pobrań na koncie. Napastnik opublikował pakiet o nazwie proc-macro1 i skopiował do niego numerację wersji prawdziwego pakietu.
Podszycie było staranne. Katalog z kodem źródłowym zawierał autentyczną zawartość proc-macro2, więc każdy projekt, który to wciągnął, budował się normalnie. W metadanych podrobiono nazwisko autora prawdziwej biblioteki oraz adres repozytorium, który prowadzi donikąd. Warto to nazwać wprost: David Tolnay, którego dane podstawiono, nie ma z tą sprawą nic wspólnego. Sam padł ofiarą podszycia.
Czysta wersja proc-macro1 poszła około pięciu godzin przed tą z ładunkiem. Zgłaszający nazywa ją wprost przygotowaniem gruntu. Kiedy arrayref dokładał do siebie zależność, fałszywy pakiet miał już własną historię wydań. Z boku wyglądał więc na zwyczajną bibliotekę, która istnieje od jakiegoś czasu.
Wycofanie zdrowych wersji było przynętą
To jest element, który odróżnia ten atak od typowego podmienienia paczki. Wycofanie wersji w rejestrze pakietów jest funkcją bezpieczeństwa. Autor używa jej, gdy w wydaniu znajdzie błąd i chce zniechęcić do jego pobierania. Napastnik odwrócił to narzędzie przeciwko użytkownikom.
Z przejętego konta oznaczono jako wycofane wszystkie wersje arrayref od 0.3.5 do 0.3.9. Wolna od ostrzeżenia była za to świeżo opublikowana 0.3.10, ta z zależnością od fałszywego pakietu. Programista widział więc ostrzeżenie przy swojej stabilnej wersji i podpowiedź, żeby przejść wyżej. Wykonanie tej podpowiedzi było całym atakiem.
Zespół bezpieczeństwa Rusta zajął się tym osobno. W komunikacie napisano, że złośliwe wydanie usunięto, a wersje wycofane w złej wierze przywrócono. Skutek widać dziś w rejestrze: wydania od 0.3.5 do 0.3.9 mają znów zwykły status, a najwyższą dostępną wersją jest 0.3.9. Ta sama biblioteka ma na koncie ponad 245 milionów pobrań w całej historii, a blisko 153 miliony przypadają właśnie na tę wersję.
Skrypt budowania uruchamiał program, na który cargo już nie czekało
Rust pozwala dołączyć do pakietu skrypt, który wykonuje się przed kompilacją właściwego kodu. Służy to zwykle wykrywaniu bibliotek systemowych albo generowaniu plików. W proc-macro1 ten skrypt pobierał plik binarny z zewnętrznego adresu i go uruchamiał.
Adres serwera rozbito na kawałki zakodowane w base64 i sklejano dopiero w trakcie działania, więc przy pobieżnym czytaniu kodu nie rzucał się w oczy. Połączenie szło przez bibliotekę szyfrującą z podstawionym weryfikatorem certyfikatów, którego wszystkie metody sprawdzające zwracały sukces. Żadnej kontroli tam nie było. Program dobierano do systemu. Na Linuksie i macOS trafiał do katalogu na pliki tymczasowe i dostawał prawo wykonywania. Na Windowsie szedł jako skrypt PowerShella odpalany przez pośredni plik, w oknie ukrytym przed użytkownikiem.
Najlepiej pokazuje intencję autora ostatnia linijka tej procedury. Po uruchomieniu programu kod celowo porzuca uchwyt do procesu potomnego. Komentarz w źródle mówi wprost, po co: żeby proces wymknął się spod kontroli cargo i budowanie nie czekało na jego zakończenie. Kompilacja kończyła się więc w normalnym czasie, z normalnym komunikatem o powodzeniu. Warto się przy tym zatrzymać, bo w ocenie ryzyka najczęściej patrzy się na to, co aplikacja robi po uruchomieniu. Tutaj kod obcego autora wykonał się wcześniej, w chwili budowania.
Osiemdziesiąt sześć minut i sześć skasowanych pakietów
Reakcja poszła dwoma torami naraz. Zespół badawczy firmy Nextron Systems zgłosił sprawę bezpośrednio zespołowi bezpieczeństwa Rusta o 7:15 czasu uniwersalnego. Niecałe czterdzieści minut później niezależne, publiczne zgłoszenie trafiło do bazy ostrzeżeń advisory-db, wraz z analizą skryptu i wskaźnikami do wyszukania.
Czasy usunięcia opublikowano co do sekundy. Wersja arrayref 0.3.10 była dostępna 86 minut. Dla porównania, zainfekowana wersja pakietu Bitwardena wisiała w rejestrze npm 19 godzin. Dwie pozostałe biblioteki tego samego konta poszły podobną drogą: internment 0.8.7 przetrwała 90 minut, a append-only-vec 0.1.9 sto siedem. Skasowano też w całości sześć pakietów, w tym drugiego podszywacza, o którym w ostrzeżeniu napisano, że miał ten sam skrypt budowania. Trzy z tej szóstki pochodziły z jeszcze innego konta i ukazały się dwa dni wcześniej, przy czym jeden z nich usunięto wyłącznie za powiązanie z tamtym kontem.
O właścicielu przejętego konta zespół Rusta napisał ostrożnie i tak też należy to powtarzać. W komunikacie stwierdzono, że nie ma podstaw sądzić, aby autor arrayref działał w złej wierze. Jego komputer albo poświadczenia zostały prawdopodobnie przejęte, a zespół próbuje się z nim skontaktować. Jak do tego doszło, nie wiadomo, i żadne źródło tego nie rozstrzyga.
Pierwsza Misja AI · Kodożercy
Pierwszy raz z AI? Zaczynasz od zera
Pierwsza Misja AI to kurs dla osób bez technicznego przygotowania. Nie musisz pisać kodu ani znać żargonu. Dowiesz się, jak działa AI, jak promptować i jak korzystać z niej w pracy.
Wejdź na pokład →

Ile maszyn to dostało, nie wie nikt
Formalne ostrzeżenia opublikowane po incydencie zawierają przy każdej złośliwej wersji tę samą formułę: brak dowodów faktycznego użycia.
Wątek zgłoszenia rysuje obraz mniej spokojny. Sam zgłaszający pisze, że nabrał się na ostrzeżenie o wycofanej wersji. Inny programista opisuje, co zastał potem na swoim Linuksie: dwa nowe katalogi w konfiguracji użytkownika i dwa pliki wykonywalne o nazwach podszywających się pod usługi systemowe. Doszedł do tego wpis w systemd, który przywracał program po zamknięciu. Kolejna osoba opublikowała analizę pobranego pliku wykonaną na własnej maszynie roboczej. To są relacje z publicznego wątku i nikt ich niezależnie nie potwierdził. Tworzą jednak napięcie wobec formuły o braku dowodów użycia i warto o nich wiedzieć, zanim ktoś potraktuje tę formułę jako gwarancję.
Każdy atak na łańcuch dostaw kończy się tym samym pytaniem: ilu to sięgnęło. Tutaj odpowiedzi nie ma i nie należy jej podstawiać. Wspomniane 245 milionów pobrań to dorobek biblioteki od 2015 roku, a nie zasięg tego incydentu. Ilu programistów zdążyło wykonać podpowiedź cargo w ciągu tych 86 minut, nie podał nikt.
Osobna sprawa dotyczy narzędzi. Identyfikatory ostrzeżeń nadano dopiero po południu, ponad osiem godzin po zgłoszeniu. Wcześniej padło w wątku trafne pytanie. Projekt, który ma złośliwą wersję zapisaną w lokalnej pamięci podręcznej, buduje się dalej bez przeszkód, a automatyczny audyt zależności nie zgłasza niczego. Autor tego pytania zwrócił uwagę, że w wielu konfiguracjach czysty wynik takiego audytu jest traktowany jako odpowiedź na pytanie, czy problem kogoś dotyczy.
Jak sprawdzić, czy te pakiety trafiły na dysk
Zespół Rusta opublikował konkretną instrukcję wraz z gotowym poleceniem i to jest najkrótsza droga do odpowiedzi. Sprawdza się zawartość lokalnej pamięci podręcznej rejestru, czyli katalogu ~/.cargo/registry/cache, pod kątem nazw plików pobranych pakietów.
Szukać należy trzech konkretnych wersji: arrayref 0.3.10, internment 0.8.7 oraz append-only-vec 0.1.9. Poza tym dowolnej wersji pakietów, które skasowano w całości, czyli proc-macro1, proc-macro-en, aovine, arone, aronenao i tinymember. Obecność któregokolwiek z tych plików oznacza, że pakiet został pobrany na tę maszynę. To jest sygnał do dalszego sprawdzania, a nie sam w sobie dowód, że coś się uruchomiło.
Warto przy okazji zajrzeć do pliku blokującego wersje zależności w projektach z ostatnich dni. To właśnie on utrwala dokładny numer wydania. Sama publiczna informacja o incydencie nie usuwa niczego z dysku, a rejestr skasował pakiety u siebie, nie u nas. Ten sam wzorzec wracał już przy ataku na łańcuch dostaw w rejestrze npm, gdzie po ugaszeniu sprawy w rejestrze zostawało pytanie o poszczególne maszyny.
Podsumowanie
Atak na łańcuch dostaw w rejestrze crates.io trwał krótko i został ugaszony sprawnie, ale zostawia dwie rzeczy warte zapamiętania. Pierwsza to sposób zwabienia ofiar. Zamiast liczyć na pomyłkę w nazwie pakietu, napastnik przestawił wskazówkę wbudowaną w narzędzie tak, żeby sama kierowała ludzi na jego kod. Druga rzecz dotyczy momentu, w którym obcy kod się wykonuje. Skrypt budowania działa przed uruchomieniem czegokolwiek, więc do uruchomienia obcego programu wystarczyła sama kompilacja projektu. Wyglądała ona przy tym na udaną. Kto w ostatnich dniach budował coś, co ciągnie arrayref, ma dziś zadanie na kilka minut: sprawdzić lokalną pamięć podręczną rejestru pod kątem wymienionych nazw.
Newsletter · DevstockAcademy & Kodożercy
Bądź na bieżąco ze światem IT, AI i automatyzacji
Co wtorek: newsy z branży, praktyczne tipy i narzędzia które warto znać. Zero spamu.




