Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book

Supply chain attack na Pythona, czyli o krok od kolejnego dużego incydentu

23 lipca 2024, 00:23 | W biegu | 1 komentarz

Często słyszy się określenie, że bezpieczeństwo to ciągła „gra w kotka i myszkę” lub wyścig. W rzeczy samej, często badacze muszą ścigać się z przestępcami, aby zapobiec poważnym atakom. Od czasu ataku na SolarWinds, dużą popularność i rozgłos zyskują ataki na łańcuch dostaw. Na łamach sekuraka opisywaliśmy wielokrotnie sytuacje, w których podmiana lub przejęcie oprogramowania czy biblioteki doprowadzało do przejęcia całych systemów i środowisk. Taki wyścig udało się wygrać niedawno badaczom z JFrog Security Research, którzy odnaleźli Github Personal Access Token pozwalający na dostęp administracyjny do repozytoriów Pythona, PyPI oraz Python Software Foundation. Szybki czas reakcji i błyskawiczne zgłoszenie problemu spowodowało, że udostępniony publicznie token został wycofany. 

Tokeny dostępowe to jeden z rodzajów poświadczeń, często wykorzystywany w procesach automatyzacji (w miejscu klasycznych haseł). Wykorzystanie tokenów pozwala na zdefiniowanie dokładnych uprawnień, jakimi ma dysponować system legitymujący się danym poświadczeniem. Dodatkowo, oprogramowanie (np. pipeline CI/CD) stosunkowo kiepsko “klika” w fizyczne tokeny (np. Yubikeye, które wymagają fizycznego dotknięcia przycisku), a więc dostęp przy pomocy tokenów może nie wymagać dodatkowego składnika uwierzytelniania (2FA). Co za tym idzie, poufność tokenów dostępowych jest kluczowym elementem bezpieczeństwa całego systemu. Opisywaliśmy to w przypadku sprawy Mercedesa

JFrog Security oraz inne zespoły badaczy (ale też i cyberprzestępcy) przeczesują publicznie dostępne repozytoria w poszukiwaniu ciągów znaków, które mogą stanowić tokeny dostępowe (ich identyfikacja może opierać się na metodzie wystąpienia np. ciągu „token” lub badaniu entropii i innych cech świadczących o tym, że określony string to poświadczenia). Jednym z narzędzi, które automatycznie przeszukuje repozytoria jest trufflehog, który może zostać zintegrowany z pipeline CI/CD (np. GitHub Actions), aby informować o zaistniałym wycieku. Podobne mechanizmy można zintegrować z hookami systemu kontroli wersji, aby blokowały one wypchnięcie do repozytorium sekretów. 

Jednak przypadek Pythona jest inny, dużo bardziej niebezpieczny oraz wyjątkowy, ponieważ nie do końca dotyczy kodu źródłowego… 

Zidentyfikowany token miał dostęp administracyjny do:

  • 91 repozytoriów Pythona,
  • 55 repozytoriów Python Packing Authority,
  • 42 repozytoriów Python Software Foundation,
  • 21 repozytoriów Python Package Index.

Już same nazwy organizacji mogą wprowadzić osoby zajmujące się bezpieczeństwem w odrętwienie. Dostęp do takich zasobów mógł pozwolić na szeroko zakrojoną i dobrze ukrytą operację ataku na łańcuch dostaw (podobną do tej z xz, ale wykorzystującą znacznie więcej projektów).

Rysunek 1. Schemat ataku na ekosystem Pythona (źródło: jfrog.com)

Jednym ze scenariuszy było przejęcie infrastruktury PyPI, służącej do dystrybucji paczek Pythona. Dzięki temu wgrywane do repozytorium paczki mogłyby być backdoorowane. 

Rysunek 2. Schemat przykładowego ataku z wykorzystaniem PyPI (źródło: jfrog.com)

Standardowo zalecane jest skanowanie kodu źródłowego w poszukiwaniu sekretów, które nie powinny opuścić maszyny developera lub bezpiecznego vaulta. W opisywanym przypadku takie sprawdzenie, chociaż potrzebne, byłoby bezużyteczne. Okazało się, że w kodzie źródłowym rzeczony token nie występował. 

Wiadomo, że był on obecny tylko w jednym pliku: __pycache__/build.cpython-311.pyc. Python w momencie wykorzystania słowa kluczowego „import” dokonuje kompilacji kodu źródłowego na bajtkod maszyny wirtualnej. Jest to krok pozwalający między innymi na optymalizację szybkości działania. Jest to więc plik binarny. 

Rysunek 3. Hexdump skompilowanego pliku pyc zawierający rzeczony GitHub PAT (źródło: jfrog.com)

Dlaczego tak się stało? Osoba, która niechcący wrzuciła do repozytorium plik .pyc z PAT dodała w celach testowych token, uruchomiła skrypt (który został skompilowany), usunęła PAT z kodu źródłowego (nie uruchamiając i nie usuwając plików pyc) i następnie umieściła kod źródłowy oraz skompilowaną binarkę w repozytorium.

Badacze podkreślają ważny fakt, że tego typu wypadki będą się zdarzać. Kluczem do sukcesu jest duża higiena poświadczeń pozwalająca na minimalizację ryzyka, ciągłe testowanie publikowanych plików oraz reakcja, jeśli incydent już wystąpi. 17 minut wystarczyło zespołowi PyPI do obsłużenia tego zgłoszenia, co jest imponującym wynikiem. Przy okazji pozwoliło to zapobiec bardzo groźnym skutkom. 

Przedstawiona sytuacja pokazuje też jak ważne jest stosowanie principle of least privilege, czyli wiązanie poświadczeń z najmniejszym możliwym scope (np. pojedyncze repozytorium) i najmniejszymi możliwymi uprawnieniami pozwalającymi na wykonanie pracy. Dzięki temu nie dojdzie do sytuacji, w której wyciek jednego tokenu naruszy bezpieczeństwo milionów linii kodu. Długość „życia” tokenów również powinna być dobrana w taki sposób, aby czas pozwalający na skuteczny atak był jak najkrótszy. 

Godnym pochwały jest również zachowanie zespołu PyPI, który opublikował dokładny timeline oraz powód wystąpienia incydentu.

Listing 1. Niechciane zmiany wykonane „z lenistwa” (źródło: blog.pypi.org)

diff --git a/cabotage/celery/tasks/build.py b/cabotage/celery/tasks/build.py
index 0f58158..3b88b5d 100644
--- a/cabotage/celery/tasks/build.py
+++ b/cabotage/celery/tasks/build.py
@@ -395,7 +395,10 @@ def build_release_buildkit(release):


 def _fetch_github_file(
-    github_repository="owner/repo", ref="main", access_token=None, filename="Dockerfile"
+    github_repository="owner/repo",
+    ref="main",
+    access_token="0d6a9bb5af126f73350a2afc058492765446aaad",
+    filename="Dockerfile",
 ):
    g = Github(access_token)
    try:
@@ -407,7 +410,13 @@ def _fetch_github_file(
        return None

Ee Durbin przyznał, że był świadomy zagrożenia jakie niesie za sobą brak poprawnej konfiguracji aplikacji GitHuba i obejście tego przez podanie własnego tokenu dostępowego. Przyznał także, że jego .dockerignore nie zawierał plików .pyc, co bezpośrednio przyczyniło się do wycieku. 

Każdy taki incydent to lekcja dla wszystkich. W tym przypadku udało się uniknąć konsekwencji. Warto więc wyciągnąć lekcje z zaistniałej sytuacji. Zespołowi jFrog gratulujemy znaleziska, a developerom Pythona wzorowej reakcji. Nie myli się ten kto nic nie robi, czasami profesjonalizm i dojrzałość organizacji można poznać po zachowaniu, gdy dojdzie już do nieoczekiwanego zdarzenia. 

~fc

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. SeeM

    Historia o tyle ciekawa, że gotowe pliki gitignore do kodu pythonowego są jakby… wszędzie. Ja też mam trochę stresu przy commitach kodu zawierającego jakieś tokeny w konfiguracji. Najlepiej mi się sprawdza dodawanie do repozytorium plików cośtam.cfg.example, z pominięciem moich plików cośtam.cfg . Sporo projektów tak robi i nie ma co wymyślać koła.

    Odpowiedz

Odpowiedz