XZ Utils. Backdoor, którego omal nie dostał cały Linux
Andres Freund nie szukał backdoora. Próbował ustalić, dlaczego logowanie przez SSH na testowym Debianie trwa o pół sekundy dłużej, a proces sshd zużywa zaskakująco dużo procesora. Pod koniec marca 2024 roku doszedł do biblioteki liblzma, części pakietu XZ Utils. Dwa dni później napisał na listę oss-security, że wydania 5.6.0 i 5.6.1 zawierają kod przygotowany do ingerencji w uwierzytelnianie SSH.
Nie był to błąd programistyczny. Ktoś cierpliwie wszedł do małego projektu open source, zdobył uprawnienia maintenera, przygotował złośliwe archiwa wydań i próbował doprowadzić do ich włączenia w nowe wersje dystrybucji Linuksa.
Plan prawie się udał. Zatrzymało go opóźnienie, którego większość administratorów uznałaby za zwykłą usterkę wydajności.
Biblioteka, której prawie nikt nie zauważa
XZ Utils służy do kompresji danych. Polecenie xz, format .xz i biblioteka liblzma są obecne w dystrybucjach Linuksa, narzędziach budowania pakietów i archiwach z kodem. To infrastruktura, która ma działać w tle. Użytkownik rzadko podejmuje decyzję, że chce ją zainstalować. Dostaje ją jako zależność czegoś innego.
Projekt przez lata rozwijał głównie Lasse Collin. Jak wiele elementów linuksowego zaplecza, był ważny dla ogromnej liczby systemów i zależny od czasu niewielu osób. Collin pisał publicznie, że projekt jest niepłatnym zajęciem, a jego możliwości są ograniczone również z powodów zdrowotnych.
W październiku 2021 roku osoba używająca nazwy Jia Tan wysłała pierwszą niewinną poprawkę. Kolejne też wyglądały zwyczajnie. Naprawy testów, porządki w systemie budowania, odpowiedzi na zgłoszenia. W tym samym czasie inne, praktycznie nieznane wcześniej konta naciskały na Collina, zarzucając mu opieszałość i domagając się przekazania projektu komuś bardziej aktywnemu.
Zachowane wiadomości nie dowodzą, kto obsługiwał te konta. Układ jest jednak trudny do zignorowania. Jedna osoba budowała reputację pomocnego współpracownika, a kilka innych podważało pozycję przeciążonego maintenera. W 2022 roku Jia Tan dostał dostęp do repozytorium. W 2023 samodzielnie przygotowywał już wydania.
Było to powolne zdobywanie dostępu, porównywalne z zatrudnieniem się w firmie tylko po to, żeby po dwóch latach otrzymać klucz do serwerowni.
Kod schowany poza kodem
W lutym 2024 Jia Tan wydał XZ Utils 5.6.0. W marcu pojawiła się wersja 5.6.1. Złośliwy mechanizm nie leżał w repozytorium w postaci czytelnego pliku z podejrzaną funkcją. Część danych ukryto w binarnych plikach testowych. Dodatkowy fragment procesu budowania znajdował się w opublikowanych archiwach tar, ale nie w normalnym drzewie kodu.
Ta różnica miała znaczenie. Dystrybucje często pobierają podpisane archiwum wydania i budują z niego pakiet. Recenzent oglądający samą historię zmian w repozytorium nie widział pełnej zawartości tego, co ostatecznie trafiało do kompilacji.
Podczas budowania pakietu dla określonego środowiska skrypt wyciągał kolejne fragmenty z plików udających dane testowe. Powstający obiekt był dołączany do liblzma. Mechanizm uruchamiał się tylko przy wąskim zestawie warunków: architektura x86-64, konkretne narzędzia budowania, pakiet w formacie używanym przez Debiana lub Red Hata oraz sshd połączony pośrednio z biblioteką przez systemd.
Na pasującym systemie zmodyfikowana biblioteka mogła ingerować w proces uwierzytelniania SSH. CERT-EU opisał możliwość wykonania polecenia przed zalogowaniem przez osobę posiadającą odpowiedni klucz prywatny. Nie był to więc backdoor otwarty dla każdego skanera w internecie. Dostęp miał pozostać zamknięty dla autora operacji.
Precyzja ograniczała ryzyko przypadkowego odkrycia. Jednocześnie zdradzała, że celem nie była biblioteka kompresji sama w sobie. XZ był drogą do procesu, który przyjmuje połączenia administracyjne na serwerach.
Pół sekundy
Freund pracował nad wydajnością PostgreSQL i testował zachowanie nowych wersji Debiana. Zauważył, że kończenie procesów sshd zużywa więcej procesora niż powinno. Pojawiały się też błędy narzędzia Valgrind. Zaczął porównywać biblioteki i profile wykonania.
Trop prowadził przez libsystemd do liblzma. To połączenie samo w sobie wygląda dziwnie, bo serwer SSH nie potrzebuje kompresji XZ do sprawdzania klucza użytkownika. Zależność pojawiała się przez mechanizm powiadomień systemd. Właśnie tę drogę wykorzystał złośliwy kod.
27 marca Freund skontaktował się z zespołem bezpieczeństwa Debiana. Informacja trafiła też do Red Hata i innych dostawców. 29 marca opublikował analizę. CISA, Red Hat, Debian, Fedora i pozostali zaczęli usuwać wadliwe paczki, cofać wersje i sprawdzać własne systemy budowania.
Stabilne wydania większości dużych dystrybucji nie zdążyły przyjąć zainfekowanej wersji. Zagrożone były głównie gałęzie testowe i szybko aktualizowane, między innymi Fedora 40 beta, Fedora Rawhide, Debian testing i unstable oraz openSUSE Tumbleweed. Red Hat Enterprise Linux nie zawierał podatnych paczek. Ubuntu również nie wydało ich w stabilnym kanale.
Określenie “omal nie dostał cały Linux” jest więc skrótem, nie opisem faktycznej infekcji wszystkich maszyn. XZ jest szeroko używany, ale backdoor miał wąskie warunki uruchomienia, a złośliwe wydania zatrzymano przed wejściem do głównych stabilnych dystrybucji. Potencjalny zasięg był ogromny. Rzeczywisty pozostał mały.
Dlaczego skanery go nie zatrzymały
Atak wykorzystał kilka miejsc, które zwykle traktuje się osobno. Konto maintenera wyglądało wiarygodnie, bo przez dwa lata dostarczało prawidłowe poprawki. Repozytorium nie zawierało całego mechanizmu obecnego w archiwum wydania. Złośliwe dane ukryto w plikach testowych, gdzie losowy binarny materiał nie budzi podejrzeń. Kod aktywował się tylko w wybranym środowisku.
Red Hat podał, że wyszukiwanie próbki obiektu w VirusTotal nie zwróciło trafień. Narzędzia antywirusowe nie miały wcześniej próbki ani prostego ciągu znaków do rozpoznania.
Podpis kryptograficzny też nie rozwiązywał problemu. Archiwa zostały podpisane przez osobę uprawnioną do wydawania XZ. Podpis potwierdzał pochodzenie pliku. Nie potwierdzał uczciwości maintenera ani zgodności archiwum z repozytorium.
To samo dotyczy popularnej rady, by używać tylko open source, bo kod może obejrzeć każdy. Może. Tylko że ktoś musi jeszcze porównać repozytorium z tarballem, rozpakować binarne pliki testowe, prześledzić warunkowy skrypt budowania i zauważyć dodatkowe pół sekundy przy SSH. Możliwość audytu nie jest audytem.
Maintainer jako powierzchnia ataku
Po incydencie łatwo zrobić z Collina człowieka, który “oddał projekt obcej osobie”. To wygodna wersja dla firm opierających produkty na darmowych bibliotekach i niepłacących za ich utrzymanie. XZ było elementem krytycznej infrastruktury, a odpowiedzialność skupiała się na osobie, która publicznie pisała o braku czasu i problemach ze zdrowiem.
OpenSSF i OpenJS ostrzegły później przed podobnymi próbami przejmowania projektów. Schemat nie wymaga zaczynania od złośliwego kodu. Najpierw pojawia się pomocny kontrybutor. Potem presja od pozornie niezależnych użytkowników. Na końcu propozycja rozwiązania problemu przez nadanie uprawnień.
Nie każda nowa osoba jest napastnikiem. Projekt, który nikomu nie ufa, również umrze. Sensowna odpowiedź dotyczy procesu: co najmniej dwóch osób przy wydaniu, odtwarzalne buildy, porównywanie artefaktów z repozytorium, rozdzielenie prawa do zatwierdzania kodu od prawa do publikowania paczek i finansowanie pracy, od której zależą cudze serwery.
Żadna z tych rzeczy pojedynczo nie zatrzyma operacji przygotowywanej latami. Razem zwiększają liczbę miejsc, w których ktoś musi popełnić widoczny błąd.
Incydent, który nie stał się katastrofą
Nie ma publicznych dowodów, że backdoor został użyty przeciw realnym systemom. Nie znamy też potwierdzonej tożsamości osoby lub grupy za nazwą Jia Tan. Skala przygotowań sugeruje zasoby i cierpliwość, ale wskazanie konkretnego państwa bez dowodów byłoby dopisywaniem zakończenia.
Wiemy za to, co zostało zrobione. Ktoś przez ponad dwa lata zdobywał zaufanie w projekcie, przygotował złośliwe wydania i naciskał na dystrybucje, aby szybko je przyjęły. Techniczna część była skomplikowana. Wejście zaczęło się od zwykłych maili i zaległych zgłoszeń.
XZ nie uratował genialny system bezpieczeństwa łańcucha dostaw. Uratował go inżynier, którego zirytowało wolniejsze logowanie. To dobry powód, żeby nie nazywać sprawy sukcesem zabezpieczeń open source. Katastrofy nie było, ale mechanizm dotarł wystarczająco daleko, by pytanie nie brzmiało, czy podobna próba się powtórzy. Bardziej użyteczne jest pytanie, w ilu projektach już trwa i nie spowalnia SSH.
Źródła
- Andres Freund: pierwsze publiczne zgłoszenie backdoora na liście oss-security
- Red Hat: przebieg wykrycia i koordynacji incydentu
- CERT-EU: opis CVE-2024-3094, dotkniętych systemów i łańcucha wykonania
- OpenSSF: warunki aktywacji backdoora w procesie budowania
- OpenSSF i OpenJS: ostrzeżenie o przejmowaniu projektów przez socjotechnikę
- Russ Cox: oś czasu przejęcia projektu XZ Utils