
Jak mówi popularny wśród programistów żarcik: gdyby domy były budowane tak jak oprogramowanie, jeden dzięcioł mógłby zniszczyć całą cywilizację.
Doświadczeni programiści wiedzą jednak, że w sumie to wcale nie jest żart – do spowodowania globalnej katastrofy w IT na serio wystarczyłoby pstryknąć we właściwe miejsce. A takich właściwych miejsc jest dużo więcej, niż się komukolwiek spoza branży może wydawać, i nie zawsze są one tak starannie chronione, jak byśmy chcieli.
Żeby pokazać, na jak niepewnych fundamentach to wszystko stoi, zacznijmy od kwestii bibliotek.
Nie takich z książkami – w języku programistów tym słowem określa się kawałek kodu służący do – cóż, czegokolwiek – i przeznaczony do wpięcia w większą aplikację. Mamy biblioteki do rozmaitych zaawansowanych obliczeń, do skalowania obrazków, do komunikacji z bazą danych, do generowania kodów kreskowych, do rozpoznawania kraju po współrzędnych geograficznych – jako się rzekło, do czegokolwiek, co może być potrzebne w wielu różnych aplikacjach.
Fajną cechą programistów (przynajmniej niektórych) jest to, że chętnie się dzielą swoimi bibliotekami, udostępniając ich kod za darmo w sieci, dzięki czemu ich koledzy po fachu nie muszą wynajdywać koła na nowo, kiedy potrzebują w swoim kodzie przeskalować obrazek czy coś – wystarczy pobrać odpowiednią bibliotekę, wpiąć w swój kod i jej użyć, zamiast przez wiele dni doktoryzować się z tajników zapisu jotpegów czy gifów.
Problem w tym, że biblioteki często same korzystają z innych bibliotek: nasza biblioteka do przetwarzania obrazków może się wspomagać biblioteką do obsługi plików, biblioteką do operacji matematycznych, biblioteką do kompresji danych i czymś tam jeszcze – jak zatem ją udostępniać? Razem z bibliotekami zależnymi, czy samą, jedynie informując w dokumentacji o wymaganiach?
Pierwszy sposób wydaje się prostszy – ale co, jeśli ktoś już używa którejś z tych dodatkowych bibliotek? Niektóre języki programowania pozwolą podpiąć tę samą bibliotekę wielokrotnie (co nie znaczy, że jest to wskazane), ale większość zaprotestuje, zmuszając programistę do ręcznego rozwiązywania konfliktu, co może się łączyć z dłubaniem w kodzie „głównej” biblioteki, a tego chcielibyśmy uniknąć. Sama kwestia podpinania bywa zresztą nietrywialna.
Drugi sposób oszczędza nam tej komplikacji, ale w zamian dodaje masę roboty z instalowaniem zależności: chcesz podpiąć jedną bibliotekę, a tu trzeba dodać trzy kolejne, które mogą wymagać jeszcze kolejnych – i zaraz może się okazać, że ogarnięcie tego wszystkiego zajmie więcej czasu niż napisanie własnej biblioteki.
Problem został rozwiązany w taki sposób, w jaki programiści zazwyczaj rozwiązują problemy, czyli przez zwalenie czarnej roboty na komputery: powstały takie narzędzia jak composer czy npm, które pozwalają umieścić wymagane biblioteki w pliku konfiguracyjnym, a przy instalacji automatycznie dociągnąć i doinstalować zależności biblioteki, jak również ich zależności, jak również zależności zależności… i tak dalej, dbając o to, żeby wszystko się spinało bez zawracania głowy programiście. A ponieważ oba wspomniane narzędzia stały się powszechnym standardem, prawie każda szanująca się biblioteka zawiera plik konfiguracyjny któregoś z nich, dzięki czemu może być przez nie obsłużona.
Wszystko pięknie, ale jak to często w życiu bywa, rozwiązanie jednego problemu stało się źródłem kolejnych.
Niefajną cechą programistów (przynajmniej niektórych) jest lenistwo – kiedy więc instalowanie bibliotek stało się banalnie proste, wielu zaczęło je instalować z byle powodu. Po co tracić pięć minut na napisanie kawałka kodu, skoro można w kilka sekund podpiąć megabajtową bibliotekę z dwudziestoma zależnościami, która oprócz tysiąca innych funkcji ma tę jedną, której nam potrzeba? Po nic, bo co nas te megabajty kosztują. A potem ktoś podpina naszą bibliotekę jako zależność do swojej, często z równie błahego powodu, a potem ktoś inny podpina tamtą bibliotekę… I w efekcie dostaliśmy hiperinflację zależności, coraz częściej podpinanych zupełnie niepotrzebnie.
Żeby było jasne, o jaki poziom lenistwa momentami chodzi, odnotujmy istnienie biblioteki is-odd, służącej wyłącznie do sprawdzania, czy liczba jest nieparzysta – co i bez jej pomocy da się zrobić w jednej linijce kodu. Powstało toto ewidentnie dla beki – ale ma, uwaga, prawie trzysta tysięcy pobrań tygodniowo i 126 bibliotek, które tego używają (wiele z nich także powstało tylko dla beki, ale daleko nie wszystkie). To jak, straciliście już wiarę w ludzkość?
Dobry przykład efektów tej inflacji to SKIIIIIIIIIIIIIIID (tak, pisane przez piętnaście „i”) – gierka, w której wszystkie elementy graficzne można literalnie na palcach policzyć (nawet bez użycia kciuków) i wszystkie są prościutkimi monochromatycznymi rysunkami, oprawa dźwiękowa to jedna muzyczka w tle, gameplay wydaje się do zakodowania w kilkunastu kilobajtach (no, niechby kilkudziesięciu, może ta fizyka jazdy jest bardziej zaawansowana niż się zdaje), a cała otoczka to ekran tytułowy i wybór levelu (jedno i drugie na maksa prymitywne).
W latach 80-tych programiści potrafili upychać gierki o podobnym poziomie złożoności w kilku kilobajtach, jeszcze ogarniając samodzielnie różne rzeczy, które dziś załatwia gratisowo sam system operacyjny. No ale wtedy pisanie gier było sportem ekstremalnym, a dzisiaj nie ma potrzeby aż tak kombinować. Ile zatem takie coś może zajmować dzisiaj, bez oszczędzania na siłę, ale i bez przesadnej rozrzutności – megabajt?
No, niechby nawet dziesięć.
Ale nie. Zajmuje – uwaga – 239 megabajtów. Prawie ćwierć giga. Czujecie klimat?
Co prawda nawet przy większej dbałości o optymalizację proporcje nie są tak znowu wiele lepsze: projekt, przy którym pracuję, ma około czterech megabajtów własnego kodu – i prawie trzysta mega bibliotek. A możecie mi wierzyć, że nie podpinam niczego bez powodu.

To, że te nadmiarowe zależności marnują miejsce na dysku, nie jest jednak wielkim problemem – kod ma to do siebie, że zajmuje bardzo mało miejsca w porównaniu z grafiką czy dźwiękiem, więc mimo hiperinflacji ciągle mówimy o megabajtach, a nie gigabajtach.
Poważniejszym problemem jest praktyczna niemożliwość ogarnięcia tego, co sobie instalujemy – przy setkach bibliotek nie ma mowy, żeby się orientować, czego tam właściwie używamy i co to robi. A przecież wystarczy luka bezpieczeństwa tylko w jednej z nich, żebyśmy mieli poważny problem, bo każdy łańcuch jest tylko tak silny, jak jego najsłabsze ogniwo.
Fajną cechą programistów (przynajmniej niektórych) jest to, że lubią zaglądać w cudzy kod i szukać w nim błędów – każda publicznie dostępna biblioteka zostaje więc szybko zbadana, a jak ktoś znajdzie w niej dziurę, to zgłasza autorom, a po jej załataniu ogłasza się wszem i wobec, że wersja taka i taka była dziurawa, więc jeśli ją macie, to natychmiast zaktualizujcie. Jeśli zatem trzymamy rękę na pulsie i pamiętamy o regularnych aktualizacjach, ryzyko jest niewielkie.
Ale niezerowe.
A źli ludzie nie próżnują i na różne sposoby próbują podsuwać programistom biblioteki zawierające szkodliwy kod. Na przykład publikując kopie popularnych bibliotek pod łudząco podobnymi nazwami, z jedną zmienioną lub opuszczoną literką, w nadziei, że ktoś zrobi literówkę przy wpisywaniu, lub że używa AI, która użyje nieprawidłowej nazwy sama z siebie (bo jak AI czegoś nie wie, to zmyśla – a to daje możliwości). Albo włamując się na konta autorów popularnych bibliotek. Albo pomagając im w rozwijaniu kodu, żeby zyskać ich zaufanie i z czasem dorobić się uprawnień do samodzielnego aktualizowania biblioteki.
I ten ostatni przypadek rok temu o mało co nie doprowadził do największej katastrofy w historii branży IT.
Istnieje sobie bowiem biblioteka o niezbyt wyrafinowanej nazwie xz, służąca do pakowania plików (tak jak zip czy tar), nawet wśród programistów mało znana, ale tu i ówdzie się jej używa. Od lat rozwijał ją hobbystycznie jeden koleś – i po iluś latach trochę mu się odechciało, więc aktualizacje wrzucał coraz rzadziej, a użytkownicy narzekali. Na szczęście w sieci znalazł się ktoś, kto go zaczął wspomagać, podrzucając poprawki i wykazując większą ochotę do pracy nad biblioteką niż sam autor – ten zatem w końcu pozwolił mu wprowadzać zmiany w kodzie bez konieczności akceptacji i wszyscy byli szczęśliwi.
Do czasu.
Istnieje sobie bowiem coś takiego jak SSH – protokół do komunikacji z serwerem, pozwalający zdalnie wykonywać na nim polecenia i powszechnie używany przez programistów i adminów do wszelkich możliwych działań na serwerach. Popularną aplikacją do obsługi tego protokołu od strony serwera jest OpenSSH – gdyby więc znalazła się tam jakaś luka w kodzie, pozwalająca na nieautoryzowane połączenie, każdy serwer z tą aplikacją znalazłby się w śmiertelnym niebezpieczeństwie, jako że przez SSH można zrobić zasadniczo wszystko, co tylko chcemy.
No i pewnego pięknego dnia pewien tester zauważył dziwne zachowanie tego programu po najnowszej, nieopublikowanej jeszcze aktualizacji: nieudane próby logowania trwały ciut dłużej, niż do tej pory – ułamek sekundy, którego normalnie nikt by nie zauważył, ale automatyczne testy mierzyły czas z dużą dokładnością, pozwalając zauważyć anomalię. Tester zaczął drążyć i znalazł kawałek kodu, który w przypadku logowania niepoprawnym kluczem dokonywał sprawdzania klucza raz jeszcze – ale nie tego, którego użyto, tylko jakiegoś innego, zapisanego na sztywno w kodzie. Gdyby więc aktualizacja została opublikowana, właściciel tego klucza mógłby się zalogować na każdy serwer z najnowszą wersją OpenSSH – i zrobić na nim, co mu się żywnie podoba.
Jak się jednak okazało, takiego kodu nie było w repozytorium – pojawiał się dopiero w zainstalowanej aplikacji. Jak się okazało po dalszym dochodzeniu, jego źródłem była wspomniana wcześniej biblioteka xz, używana do rozpakowywania plików przy instalacji. Ktoś (chyba nie trzeba dodawać, kto) wprowadził do niej kod, który przy rozpakowywaniu pliku sprawdzał, z czym ma do czynienia, i jeśli znalazł pewien charakterystyczny dla OpenSSH fragment, dopisywał w odpowiednim miejscu własne zmiany.
Gdyby ta aktualizacja została klepnięta i zainstalowana na tysiącach serwerów, byłby to zapewne największy włam w historii. Oczywiście wiele zależy od tego, kim był sprawca i jakie miał zamiary – tajne służby jakiegoś państwa z pewnością wykorzystałyby taką supermoc sprawniej niż samotny haker – ale konsekwencje z pewnością byłyby ogromne. Ile firm i instytucji dałoby się dzięki temu zhakować, ile danych ukraść, ile zamętu narobić – strach pomyśleć. A wszystko dzięki przemyceniu jednej modyfikacji do jednej mało znanej biblioteki.

No więc pomyślcie sobie teraz, że takich małych bibliotek, na których stoi pół internetu, a które rozwijają w czynie społecznym jacyś przypadkowi (nierzadko anonimowi) ludzie, są setki.
Fakt, że mimo to ciągle do żadnej apokalipsy jeszcze nie doszło, trochę podnosi poziom wiary w ludzkość – a przynajmniej tę jej część, która czuwa nad bezpieczeństwem systemów.





