Przejdź do treści
INDEKS ANALIZ BREACHROAD / NOTA TECHNICZNA

Rust: arrayref, internment i append-only-vec złośliwe w czasie budowania

Trzy przejęte wersje crates.io uruchamiały złośliwy build script. Wyjaśniamy okno 86–107 minut, cache Cargo, lockfile, CI i pełną odpowiedź.

MATERIAŁ PUBLICZNY
AUTOR
/ CEO Breachroad · OSCP · PNPT
PUBLIKACJA
21 sierpnia 2026
CZAS CZYTANIA
18 min czytania
TEMAT
Łańcuch dostaw
Rust: arrayref, internment i append-only-vec złośliwe w czasie budowania

21 sierpnia rekordy RustSec i CVE uzupełniły obraz ataku na łańcuch dostaw crates.io. Złośliwe były konkretne wersje: arrayref 0.3.10, internment 0.8.7 oraz append-only-vec 0.1.9. Każda otrzymała bezpośrednią zależność od proc-macro1, której build script pobierał złośliwy payload. Zespół Rust usunął pakiety, yankował przejęte wydania, przywrócił wcześniej złośliwie yankowane wersje i zablokował konto autora jako środek ostrożności.

To kompromitacja, nie zwykła podatność. Nie trzeba wywołać wadliwej funkcji aplikacji. Kod build script działa podczas pobierania lub kompilowania zależności z uprawnieniami procesu cargo, często na laptopie programisty albo runnerze CI mającym dostęp do kodu, tokenów rejestrów, cache, artefaktów i mechanizmu publikacji.

Dokładne okno ekspozycji

Rust Security Response Team podał precyzyjne czasy:

RustSec podaje, że złośliwy arrayref pobrano 2285 razy, mniej niż 10% ruchu wszystkich wersji pakietu, ponieważ wiele projektów miało starszą wersję w lockfile. Krótkie okno zmniejsza skalę, ale go nie zeruje: automatyczne pipeline’y i boty aktualizacyjne potrafią pobrać nowe wydanie w kilka minut.

Wszystkie wersje proc-macro1, proc-macro-en, aovine, arone, aronenao i tinymember zostały wskazane do usunięcia. Rust informuje, że autor popularnego arrayref prawdopodobnie nie działał złośliwie; przejęte mogły być jego komputer lub poświadczenia. To ważne rozróżnienie dla komunikacji incydentu, ale nie zmienia technicznego obowiązku sprawdzenia środowisk.

Dlaczego Cargo.lock pomaga, ale nie gwarantuje bezpieczeństwa

Projekt aplikacyjny z committowanym Cargo.lock zwykle pozostaje przy wcześniej rozwiązanej wersji, dopóki ktoś nie wykona aktualizacji. To ograniczyło pobrania 0.3.10. Biblioteki często nie commitują lockfile albo pipeline uruchamia cargo update, regeneruje lock lub buduje przykłady na świeżym resolverze. Zależność może też wejść tranzytywnie przez inny crate.

Yank i usunięcie z rejestru nie cofają lokalnego cache ani artefaktu już zbudowanego. Runner może zachować archiwum w ~/.cargo/registry/cache, mirror może skopiować paczkę, a kontener buildowy może zostać opublikowany. Dlatego odpowiedź nie kończy się na ponownym uruchomieniu cargo build po usunięciu wersji.

Pierwszy krok: ustal, czy pakiet został pobrany lub zbudowany

Sprawdź Cargo.lock we wszystkich branchach i release tags, logi resolvera, cache Cargo na stacjach i runnerach, dependency proxy oraz SBOM artefaktów. Szukaj dokładnych wersji, a także wymienionych pakietów pośrednich. cargo tree na bieżącym branchu nie wystarczy, jeśli złośliwa wersja była obecna tylko w krótkotrwałym buildzie, który później zniknął.

Zachowaj metadane: czas utworzenia cache, job ID, hash obrazu, commit, użytkownika runnera i listę sekretów dostępnych procesowi. Nie uruchamiaj znalezionego archiwum ani jego build scriptu w celu „potwierdzenia”. Dowodem ekspozycji jest pobranie lub kompilacja w oknie kampanii; dalsza analiza złośliwego kodu należy do izolowanego laboratorium malware.

Rust Team podał defensywne wyszukiwanie plików cache. W środowisku firmowym lepiej wykonać je przez EDR lub narzędzie zarządzania, aby nie przepisywać ścieżek ręcznie na setkach hostów. Wyniki powinny wracać jako hash i ścieżka, a nie kopia podejrzanego pakietu wysyłana do ticketu.

Jeżeli złośliwa wersja tylko pojawiła się w lockfile

Sam wpis w lockfile dowodzi rozwiązania zależności, ale nie zawsze wykonania build scriptu. Ustal, czy job pobrał i kompilował crate. Jeżeli pipeline zakończył się przed fazą build albo użył już gotowego artefaktu, wpływ może być mniejszy. Nie traktuj jednak braku połączenia C2 w jednym logu jako dowodu bezpieczeństwa: logi DNS, proxy lub EDR mogą mieć luki.

Usuń złośliwą wersję z lockfile przez powrót do wersji nieobjętej incydentem: arrayref <=0.3.9, internment <=0.8.6, append-only-vec <=0.1.8. RustSec nie wskazuje „patched version”, ponieważ złośliwe wydania usunięto; określa starsze zakresy jako unaffected. Po zmianie odtwórz build z czystego cache i zweryfikowanego źródła.

Jeżeli build script został uruchomiony

Traktuj runner lub stację jako potencjalnie naruszoną. Odłącz ją logicznie od mechanizmu publikacji, ale zabezpiecz pamięć, logi procesu, historię sieci, cache Cargo, workspace i dane EDR. Nie wykonuj tylko cargo clean: usuwa część dowodów i nie odwraca działań payloadu.

Zidentyfikuj wszystkie sekrety dostępne jobowi: token GitHub/GitLab, credentials do crates.io i registry kontenerów, klucze podpisujące, cloud workload identity, SSH deploy keys, npm/PyPI tokens oraz zmienne środowiskowe. Rotuj je według priorytetu i dowodów. Najpierw blokuj zdolność publikacji oraz administracji; następnie poświadczenia read-only. Sprawdź też tokeny krótkotrwałe, bo aktor mógł wymienić je na trwalszy dostęp.

Zbudowane w tym jobie artefakty muszą zostać poddane kwarantannie. Porównaj provenance, hash, SBOM i podpis z buildem wykonanym na czystym runnerze. Jeżeli podejrzany artefakt został wdrożony, zbadaj runtime oddzielnie. Build-time compromise nie oznacza automatycznie, że malware trafił do finalnego binarium, ale nie wolno zakładać, że pozostał wyłącznie na runnerze.

Jak utwardzić Rust CI po incydencie

Runner budujący niezaufane zależności nie powinien jednocześnie posiadać stałych poświadczeń publikacyjnych. Rozdziel build, test, podpis i release. Do etapu release przekazuj zweryfikowany digest, a token uzyskuj przez krótkotrwałą workload identity po manualnej lub politycznej bramie. Ogranicz egress build runnera do potrzebnych rejestrów; build script pobierający payload z przypadkowej domeny powinien zostać zablokowany i zaalarmowany.

Włącz dependency review dla zmian Cargo.lock, opóźnienie automatycznego przyjmowania świeżych wersji oraz allowlistę krytycznych crate’ów. Mirror nie jest sam w sobie zabezpieczeniem, jeżeli bez zwłoki kopiuje wszystko z publicznego rejestru. Powinien mieć kwarantannę, skan, metadane pochodzenia i możliwość natychmiastowego zablokowania wersji.

Generuj SBOM oraz SLSA-style provenance na etapie build, a podpis wiąż z digestem źródła, lockfile, obrazu buildera i zależności. Dzięki temu po alarmie można znaleźć realnie dotknięte artefakty, zamiast rotować całą organizację na ślepo.

Nie zapomnij o środowiskach poza głównym CI

Atak mógł dotknąć lokalnego pre-commit, dokumentacji budowanej na osobnym runnerze, benchmarków, fuzzingu, nightly builds i automatów Dependabot/Renovate. Przejrzyj także prywatne forki, cache wykonawców oraz obrazy deweloperskie. Wiele organizacji dobrze chroni pipeline wydania, lecz utrzymuje stałe tokeny w mniej widocznych jobach jakościowych.

W rejestrze incydentu zapisz zarówno wyniki dodatnie, jak i sposób uzyskania wyników ujemnych. Stwierdzenie „nie używamy arrayref” ma wartość dopiero wtedy, gdy obejmuje wszystkie lockfile, cache, SBOM i okres retencji. Jeżeli niektórych runnerów nie da się zbadać, oznacz lukę dowodową zamiast zamieniać ją w pewność.

Fakty źródłowe i wnioski Breachroad

Nazwy i wersje pakietów, czasy publikacji i usunięcia, 2285 pobrań arrayref, rola proc-macro1 i działania crates.io pochodzą z bloga Rust oraz RustSec. CVE-2026-77649, CVE-2026-77650 i CVE-2026-77651 zostały opublikowane 21 sierpnia. Źródła nie publikują pełnej telemetrii C2 dla każdego środowiska i nie dowodzą, że każde pobranie zakończyło się kompromitacją.

Kolejność rotacji, kwarantanna artefaktów, separacja release i zasady mirrora są analizą Breachroad. Szkolenia cyberbezpieczeństwa dla deweloperów mogą przećwiczyć ten playbook, a audyt bezpieczeństwa IT sprawdzić runnerów, tokeny, egress, provenance i zarządzanie zależnościami.

Źródła

UDOSTĘPNIJ / KOPIUJ