stats

Złowrogie paczki

Ataki wymierzone w repozytoria pakietów

Fotografia opakowań spożywczych

Ataki polegające na tworzeniu fałszywych serwerów z aktualizacjami nie są tak trudne do przeprowadzenia, jak mogłoby się wydawać. Głównymi przyczynami są nieostrożność administratorów i brak szczelnych procesów publikowania wersji, chociaż możemy czasem trafić na zdumiewające wektory ataków, którym trudno zapobiegać.

Użytkownicy GNU/Linuksa wśród zalet systemu wymieniają często zautomatyzowane zarządzanie pakietami w różnych dystrybucjach. Jest to pomocne szczególnie w przypadkach szybkiego zamykania luk w kernelu i aplikacjach usługowych. Raport, który powstał na Uniwersytecie Stanu Arizona, wykazuje jednak, że menedżer pakietów może sam mieć usterki w zabezpieczeniach. Dzięki nim fałszywe serwery zapasowe z pakietami przeznaczonymi dla danej dystrybucji mogą podsuwać klientowi starsze wydania oprogramowania, które będzie podatne na ataki. Założenie takiego własnego serwera lustrzanego (ang. mirror) jest stosunkowo łatwe, co badacze zademonstrowali w opublikowanej pracy.

Zdaniem autorów opracowania – Justina Capposa, Justina Samuela, Scotta BakeraJohna H. Hartmana – ze względu na ważną rolę, jaką pełni w systemie, menedżer pakietów powinien podlegać bardzo wysokim wymaganiom dotyczącym bezpieczeństwa. Tymczasem z użyciem znalezionych przez nich luk w oprogramowaniu APT, YUM oraz YaST, pracującym pod kontrolą GNU/Linuksa i BSD, potencjalny napastnik może uzyskać dostęp do dowolnych części systemu: zmieniać pliki, kasować je, tworzyć nowe zbiory i umieszczać tylne wejście (ang. backdoor).

Fałszywe serwery

Chociaż menedżer pakietów nie łączy się z dowolnymi serwerami, w których takie luki są wykorzystywane, najwyraźniej kontrole prowadzone przez dystrybutorów przy zatwierdzaniu zewnętrznych serwerów zapasowych nie są zbyt staranne. Stosunkowo łatwo udało się badaczom umieścić zarządzany przez nich serwer na liście mirrorów dla dystrybucji Ubuntu, Fedora, openSUSE, CentOSDebian. Odnotowywali przy tym połączenia z tysiącami maszyn, wśród których były także systemy amerykańskiej armii i administracji rządowej. Według obserwacji naukowców niektóre dystrybucje sprawdzają, czy zawartość plików na dodatkowym serwerze jest zgodna z oryginałem. Można jednak utworzyć usługę, która wysyłałaby zmodyfikowaną zawartość tylko w przypadku połączenia przychodzącego od wybranych komputerów klienckich.

Napastnicy zwykle nie mogą bezpośrednio manipulować cyfrowo podpisanymi pakietami, ponieważ podczas instalacji użytkownik zostanie powiadomiony o braku właściwej sygnatury. Według autorów opracowania równie dobrze można też rozprowadzać wśród klientów stare pakiety z ważnymi podpisami, w których pojawiają się znane luki w zabezpieczeniach. Poza tym zmanipulowany mirror jest w stanie zapobiegać instalowaniu aktualizacji zabezpieczeń przekazując klientowi za każdym razem listę pakietów, która już trąci myszką. W ten sposób z biegiem czasu rośnie liczba niezałatanych luk w zabezpieczeniach systemu klienckiego, które następnie intruz może w zaplanowany sposób wykorzystać.

Trojański programista

Jeżeli potencjalnemu napastnikowi nie uda się uruchomić własnego serwera lustrzanego, może próbować podszyć się pod programistę projektu lub nawet nim zostać. Wykazując chęć niesienia pomocy, napastnik jest w stanie zyskać zaufanie zespołu odpowiedzialnego za publikowanie nowych wersji oprogramowania i we właściwym momencie przemycić do niego szkodliwy kod. W tym scenariuszu wymagana jest duża wiedza, ponieważ ryzykuje się odkrycie zamiarów, gdy któryś ze współpracowników zauważy, że fragment aplikacji jest niebezpiecznie oprogramowany. Szkodliwy kod źródłowy musi więc być tak napisany, aby sprawiał wrażenie, że został popełniony przypadkowy błąd.

Fortify Software, firma zajmująca się świadczeniem usług i produktów z zakresu bezpieczeństwa informacji, przedstawiła wyniki badań nad nowym rodzajem ataków. Cross-Build Injection (skr. XBI), bo tak nazywa się ta metoda, polega na wprowadzeniu do aplikacji niebezpiecznego kodu już na etapie jej tworzenia.

Wydawać by się mogło, że sposób ten polega dokładnie na tym samym, co znane z historii przykłady pozostawiania przez twórców aplikacji tzw. tylnych wejść. Dzięki nim programista zostawiał sobie możliwość ewentualnego przejęcia kontroli nad programem z pominięciem zabezpieczeń, w jakie go wyposażył (np. przez podanie znanej tylko sobie nazwy użytkownika i hasła). Nowość polega na tym, że w dobie zautomatyzowanych systemów kompilowania kodu, a także dzielenia procesu produkcji oprogramowania na niezależne części, więcej osób ma dostęp do zasobów decydujących o przebiegu budowania aplikacji. Za przykład posłużyć mogą mechanizmy grupowej pracy obecne w wielu projektach Open Source i Free Software, a także w korporacjach.

Winne procesy?

Tworzenie aplikacji w nowym modelu jest zazwyczaj kilkuetapowe. Kod programu znajduje się w repozytorium systemu kontroli wersji, a grupa programistów operuje właśnie na nim. Kolejną warstwą jest tworzenie paczek z wersjami gotowymi do kompilacji. Zdarza się, że działanie to jest zautomatyzowane i polega na wysłaniu do pewnego systemu żądania stworzenia archiwum z kodem pochodzącym z repozytorium. Takie archiwum źródeł (np. w formacie TGZ) jest następnie udostępniane systemom kompilująco-konsolidującym.

Kolejny krok to kompilacja – w tym przypadku mamy częściej do czynienia z pełną automatyzacją. Systemy zwane builderami otrzymują zlecenie od osoby zarządzającej wersjami (ang. release manager) i posługując się wytycznymi zawartymi w odpowiednich plikach, pobierają paczkę źródeł, rozpakowują ją, aplikują potrzebne łatki i rozpoczynają kompilację oraz budowanie pakietu gotowego do instalacji. Wspomniane wytyczne bardzo często znajdują się w repozytorium udostępnionym członkom projektu. Mowa tutaj na przykład o plikach .spec używanych przez narzędzie rpmbuild podczas budowania pakietów RPM, bądź też o instrukcjach zawartych w zbiorach build.xml, których wymaga Apache Ant.

Ataki XBI, zależnie od infrastruktury i modelu pracy nad aplikacją, mogą przybierać różną postać. Istnieje ryzyko wprowadzenia szkodliwego kodu przez osoby zaangażowane w projekt. Niebezpieczne działanie może wtedy dotyczyć zmian dokonanych w repozytorium, jednak będzie to łatwe do namierzenia. Bardziej prawdopodobnym i trudniejszym do wyśledzenia występkiem jest podmiana zarchiwizowanych źródeł zanim trafią do systemów kompilujących. Zależy to jednak od przyjętych zasad dostępu. Dotykamy tu kwestii zaufania – zdarza się, że w ramach projektu każdy programista ma na wszelki wypadek nazwę klucz pozwalający na dostęp do serwera plikowego, aby w razie pomyłki szybko zainterweniować i usunąć wadliwe archiwum.

Hack Thompsona

Poniższy fragment powstał dzięki sugestii Grzegorza Antoniaka (PGP ID: 78737BF9).

Ken Thompson to urodzony w Nowym Orleanie twórca systemu operacyjnego Unix. W roku 1983 wspólnie z Dennisem Ritchiem został wyróżniony Nagrodą Turinga za wkład w rozwój ogólnej teorii systemów operacyjnych, a dokładnie za stworzenie Uniksa. Podczas uroczystości przyznawania nagród wygłosił mowę zatytułowaną „Refleksje nad ufnością w zaufanie1”, w której przestawił typ ataku z użyciem backdoora, znany dziś jako hack Thompsona lub atak ufności w zaufanie (ang. trusting trust attack). Spisana wersja tego przemówienia traktowana jest obecnie jako jeden z elementarnych zasobów wiedzy dotyczących bezpieczeństwa informacyjnego.

Wystąpienie Thompson rozpoczął od oświadczenia, że nie będzie zbyt wiele opowiadał o Uniksie, ponieważ gros komponentów tego systemu jest efektem pracy innych ludzi, chociaż zasługi przypisywano wyłącznie jemu. Następnie podkreślił wpływ Dennisa Ritchie’ego na kierunek własnego rozwoju jako programisty i rozpoczął podzieloną na trzy etapy opowieść o najsłodszym programie, jaki kiedykolwiek napisał.

Samoreplikujący kod

W pierwszej części Thompson wspomniał lata młodości i ćwiczenia programowania na uczelni. Jedną z praktyk było stworzenie samoczynnie replikującego się kodu o jak najmniejszej liczbie linii. Nie chodziło o wirusa, lecz o program, który na standardowe wyjście wyśle własny kod źródłowy.

Thompson ilustrował wypowiedź przykładem nieco rozwlekłego zapisu w języku C, który w tablicy znakowej przechowywał niemal dokładną kopię źródłowej postaci programu.

Kura i jajko

W drugiej fazie Ken przedstawił problem kury i jajka na przykładzie kompilatora języka C, który został napisany w tym samym języku. Pokazał też fragment kodu kompilatora odpowiedzialnego za przetwarzanie symboli specjalnych w łańcuchach znakowych, w tym przypadku znaku nowej linii:

1
2
3
4
5
6
7
8
 c = next();
 if (c != '\\')
   return(c);
 c = next();
 if (c == '\\')
   return('\\');
 if (c == 'n')
   return(n);
c = next(); if (c != '\\') return(c); c = next(); if (c == '\\') return('\\'); if (c == 'n') return(n);

Warto zauważyć, że przekształcając zawierający ten fragment kod kompilatora do postaci wykonywalnej musimy użyć kompilatora, który już „rozumie” znaki odbierające specjalne znaczenie innym, następującym po nich (linie: 2, 5 i 6), ponieważ przytoczona część z nich korzysta (dzięki temu kod jest przenośny).

Ktoś mógłby dodać obsługę kolejnego znaku o specjalnym znaczeniu i po prostu dopisać kolejny warunek, np.:

 9
10
 if (c == 'v')
   return('\v');
if (c == 'v') return('\v');

Wtedy już na etapie wstępnej kompilacji pojawi się ostrzeżenie:

warning: unknown escape sequence '\v'

Dzieje się tak dlatego, że binarna wersja kompilatora, używana do przetworzenia źródła nowej, nie potrafi obsłużyć literału znakowego '\v' i pomija nieznaną sekwencję unikową (ang. escape sequence), pozostawiając wyłącznie kod znaku podanego po odwróconym ukośniku.

Receptą na powyższy kłopot jest dwuetapowa modyfikacja kompilatora. Najpierw należy użyć literału liczbowego i zwrócić np. magiczną wartość 11 z użyciem zapisu return(11); zamiast return('\v').

Po kompilacji powstanie nowa wersja binarna kompilatora z obsługą literału \v, ale nie będzie to kod, którego można użyć niezależnie od platformy – wartość 11 wpisana do pamięci może oznaczać różne rzeczy w zależności od architektury i systemu typów. Będzie jednak możliwe użycie tak przygotowanego kompilatora, aby jeszcze raz przekształcić wcześniejszy kod (zawierający literał '\v' jako wartość zwracaną) i otrzymać przenośną, binarną wersję z obsługą nowego symbolu specjalnego.

Trojan

W ostatnim etapie historii Thompson przedstawił sytuację analogiczną do opisanej wyżej, lecz jako przykładu użył funkcji kompilatora odpowiedzialnej za przetwarzanie pojedynczej linii kodu źródłowego, do której ktoś dodał trojana. Szkodliwy kod wykrywa, czy aktualnie opracowywana linia zawiera konstrukcję odpowiedzialną np. za umożliwienie dostępu do systemu (sprawdzanie hasła), a jeżeli tak jest, dołącza do niej kilka innych linii kodu. Te ostatnie to na przykład podprogram pozwalający na dostęp z użyciem uniwersalnego hasła.

Jawne umiejscowienie szkodliwego kodu w funkcji odpowiedzialnej za kompilację z pewnością wzbudziłoby podejrzenia innych programistów mających dostęp do źródłowej postaci projektu. Można jednak skorzystać z metody przedstawionej przez Thompsona, aby ukryć niebezpieczne instrukcje podsuwane w kilku fazach.

Pojawią się tu dwie grupy niebezpiecznych konstrukcji i dwa etapy kompilacji. We wstępnym etapie w kompilatorze umieszcza się dwie funkcje:

  • pierwsza zawiera kod dodający do niektórych programów tylne wejście (zmieniając zachowanie kompilatora);

  • druga odpowiada za umieszczenie pierwszej i samej siebie w wersji binarnej podczas tworzenia nowego wydania kompilatora z użyciem obecnego.

Druga funkcja jest w istocie samoczynnie powielającym się źródłem wspomnianym w pierwszej części wystąpienia Thompsona. Po pierwszej rekompilacji kompilatora szkodliwy kod źródłowy można usunąć ze źródeł, ponieważ jego binarna wersja, „zaszyta” w nowym wydaniu będzie samoczynnie się replikowała i dodawała własny źródłowy fragment do każdej „inkarnacji” narzędzia. W tym momencie kompilator pozostanie na zawsze trojanem, aż do momentu, gdy ktoś użyje zupełnie innego wydania (stworzonego przed umieszczeniem szkodliwego fragmentu) do wygenerowania nowej wersji na bazie kodu źródłowego. Może to jednak być utrudnione z powodu zależności wspomnianych przy okazji przykładu z literałami.

Opisana kwestia dotyczy nie tylko kompilatorów, ale też bibliotek programistycznych, które do kompilacji nowych wydań wymagają włączenia własnej, poprzedniej wersji. Tak naprawdę istotą problemu jest tu założenie, że ważna część większego systemu jest zawsze bezpieczna i możemy uznać ją za zaufaną. Jaki z tego wniosek?

Żaden komponent systemowy
nie może być obdarzony
bezgranicznym zaufaniem.

Ataki na konfigurację

Wróćmy do bardziej trywialnych przykładów. Kolejnym punktem krytycznym, najbardziej narażonym na ataki XBI, są zbiory z wytycznymi budowania kodu. Mowa tu o wszelkich skryptach i plikach konfiguracyjnych narzędzi, takich jak rpmbuild, ant, make, automake itp. W zbiorach tych można wskazać programowi kompilującemu aplikację lub budującemu pakiet, jakie dodatkowe czynności ma on wykonać, albo też skąd powinien ściągnąć źródła.

Powodzenie ataku zależy od tego, w którym miejscu przechowywane są pliki z instrukcjami. Zdarza się, że rezydują tam, gdzie kody aplikacji, choć w przypadku większych projektów czy dystrybucji GNU/Linuksa przeznaczone są na nie osobne systemy kontroli wersji.

Ataki kombinowane

Poważniejszym od opisanego wcześniej jest atak hybrydowy, w którym to nie uczestnik projektu umieszcza niebezpieczne kody w bibliotece czy aplikacji. Strategią zewnętrznego intruza najczęściej będzie zdyskredytowanie serwera odpowiedzialnego za budowanie nowych wersji lub systemu przechowującego pliki z instrukcjami budowania. Ataki wymierzone w repozytorium źródeł są raczej mało opłacalne, ponieważ napastnik pozostawia ślady swojej aktywności.

Ciekawy będzie atak, którego pierwszym celem jest system DNS wykorzystywany przez builder. Intruz, który wie skąd pobierane są archiwa źródeł, jest w stanie sfałszować wpis dotyczący ich lokalizacji po stronie serwera nazw i zmusić builder do pobrania własnej paczki kodu, np. z umieszczonym trojanem bądź backdoorem.

Wyniki badań pokazują, że na tego typu ataki najczęściej narażone są publiczne projekty, gdzie dostęp do kodu źródłowego ma wiele osób, a proces budowania pakietów i publikowania nowych wersji jest mocno zautomatyzowany.

Uogólniając, możemy zauważyć, że podatne na ataki kombinowane będą również duże projekty, w których nie korzysta się z publicznie widocznych stacji sieciowych, lecz pojawia się znacznie więcej sytuacji, w których przyjęty podział kompetencji nie przekłada się na zasady zarządzania dostępem. Na przykład programiści, testerzy i osoby odpowiedzialne za publikowanie wersji mają pełny dostęp do wszystkich systemów.

Ochrona

Badacze tworzący wspomniany na początku raport zawarli w nim kilka wskazówek mających na celu ochronę przed atakami wymierzonymi w serwery pakietów z oprogramowaniem. Użytkownicy powinni korzystać wyłącznie z mirrorów uruchomionych u operatorów, do których mogą mieć zaufanie. Niestety, raport nie wyjaśnia, w jaki sposób można to ustalić – na liście zapasowych serwerów dystrybucji Ubuntu znajdują się często mało znane firmy. W Polsce istnieje jednak wiele lustrzanych serwerów zasobów, które są obsługiwane przez rozpoznawalne uniwersytety. Należałoby jeszcze upewnić się, czy i one spełniają warunki bezpieczeństwa.

Poza tym operatorzy systemów powinni aktualizować je ręcznie – i zawsze wtedy, gdy wiadomo, że pojawiły się nowe wersje pakietów. Wiele tych problemów może rozwiązać menedżer pakietów Stork (z ang. bocian) zbudowany na podstawie zaplanowanej przez nich architektury bezpieczeństwa. Kwestią otwartą jest to, czy i kiedy pojawi się on w dystrybucjach GNU/Linuksa. W serwisie lwn.net toczy się dyskusja nad wynikami badań naukowców i innymi możliwymi rozwiązaniami.

Jeżeli chodzi o ochronę przed wstrzykiwaniem szkodliwych kodów już na etapie tworzenia aplikacji, nie istnieje skuteczna prewencja w otwartych projektach. Wynika to z faktu, że w świecie wolnego i otwartego oprogramowania trudno znaleźć sposoby na dyscyplinowanie osób mających niecne zamiary. W przypadku ataku wymierzonego w wiarę w zaufanie, receptą jest analiza zachowania różnych wydań oprogramowania. Gdy mamy do czynienia z kompilatorem, można na przykład zbadać czy postacie wynikowe tego samego kodu źródłowego różnią się, jeżeli zostały przekształcone z użyciem różnych wersji narzędzia.

Zobacz także


  1. K. Thompson, „Reflections on Trusting Trust”, ACM, Nowy Jork, sierpień 1984. [return]
Jesteś w sekcji .
Tematyka:

Taksonomie: