Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Kilka historii o hackowaniu pub.dev (Marcin „dla zabawy” podziałał w Google bug bounty. Z dobrym efektem.)
Firma Google stoi za wieloma projektami i produktami. Niektóre są powszechnie znane, a inne nie. Jednymi z takich mniej znanych produktów jest język programowania dart i framework flutter. Krótko po zrobieniu dość długiego tutoriala darta i fluttera wpadłem na kilka pomysłów, jak można by zaatakować komponenty ekosystemu darta. Dla zabawy zdecydowałem się spróbować swoich sił w ramach programu Google bug-bounty.
Dart i flutter to trochę jak ruby on rails. Język mocno powiązany z frameworkiem. Jak każdy ekosystem tego typu, dart ma swoje podstawowe biblioteki i repozytorium pakietów. Odpowiednik pypi czy rubygems w tym świecie nazywa się pub.dev a pip czy gem to pub.
Skupimy się na tych dwóch komponentach. Tworząc paczkę w darcie, definiujemy plik .yaml, który zawiera metadane o paczce. Plik nazywa się pubspec.yaml i wraz plikami źródłowymi jest kompresowany do tar.gz. Następnie archiwum wrzucamy do systemu, który po przetworzeniu paczki, generuje statystyki, ocenę i serwuje paczkę na żądanie.
W ramach kilku scenariuszy omówię ataki, które udało mi się z powodzeniem przeprowadzić.
Scenariusz 1
Co się stanie, jeśli plik pubspec.yaml będzie zawierał rekursywne referencje (yamlbomb)? Z uwagi na fakt, że zazwyczaj zgoda na wykonanie ataku typu DoS jest wyłączona z programu bug-bounty i ciężko zebrać jasne metryki, wypadałoby zreplikować setup lokalnie. Po kilku wieczorach i odkrywaniu tego, co powinno zostać udokumentowane, udaje się odpalić lokalną wersję pub.dev. Chwila prawdy i htop pokazuje CPU na 100%, wszystko spowalnia – BINGO!
Co się właściwie stało?
Stworzona paczka zawierająca pubspec.yaml z yamlbombą została skompresowana i wypchnięta na lokalny serwer (
PUB_HOSTED_URL=http://localhost:8080). Serwer wyekstrahował plik pubspec.yaml z archiwum. Ponieważ plik zawiera yamlbombę, to kod tworzący mapę na podstawie yamla musi wykonać masę obliczeń, wykorzystując przy tym pełną moc procesora. Jeśli cała moc obliczeniowa jest wykorzystana do parsowania yamla, to niewiele jej pozostaje do właściwej pracy i system staje się nieresponsywny.
Scenariusz 2
A gdyby do pubspec.yaml wrzucić <script>alert(document.domain)</script> jako homepage? Klient (pub) mówi, że nie wolno. Mały patch na kliencie, recompile i kolejna próba… Bingo? Nie do końca, bo choć serwer przyjął paczkę bez narzekań, to jednak CSP stoi na straży. A gdyby zamienić JS na iframe?… BINGO! Never gonna give you up…
Co się właściwie stało?
Serwer nie zwalidował strony domowej pakietu. Walidacja istniała tylko po stronie klienta, więc można było ją łatwo obejść. Wartość pola ‘homepage’ została również użyta na stronie bez escapowania. Te dwa problemy pozwoliły wstrzyknąć spreparowane dane. Wiemy, że pub.dev poprawnie implementuje CSP, więc wstrzyknięcie działającego javascriptu się nie powiodło. Mechanizm CSP nie chroni jednak przed wstrzyknięciem ramek iframe, więc ten wektor ataku okazał się sukcesem.
Scenariusz 3
Skoro wstrzyknięcie na główną stronę pakietu się udało, to rodzi się myśl, by w podobny sposób wstrzyknąć to samo,… ale przez dokumentację (docstring). Efekt, jak w poprzednim przypadku – CSP chroni przed tragedią, ale śmieszny filmik na YouTube doskonale dokumentuje metodę w klasie.
Co się właściwie stało?
Kiedy pakiet jest wstępnie przetworzony przez pub.dev, na podstawie doc-stringów asynchronicznie generowana jest dokumentacja. Doc-stringi przed pojawieniem się na stronie z dokumentacją nie były poprawnie wyescapowane. Pozwoliło to, by przygotowany payload znalazł się bezpośrednio na stronie, co z kolei umożliwiło zmiany layoutu oraz bezpośrednie wstrzyknięcie ramek iframe.
Scenariusz 4
Skoro serwer czyta archiwum tar, z którego później parsuje pubspec.yaml, to może uda się i tu coś ugrać. Niestety, rozmowa z serwerem za pomocą curla nie należy do przyjemnych, więc spójrzmy ponownie na klienta pub.
Kilka zmiana w kodzie klienta pub, recompile i kolejna próba. Tym razem paczka zawiera kilka plików pubspec.yaml w tym samym archiwum tar.gz. Ta ścieżka okazuje się niepowodzeniem, ponieważ serwer weryfikuje każdy z nich, więc nadpisywanie na nic się nie zda. Podobnie próba użycia absolutnych ścieżek w archiwum również kończy się niepowodzeniem.
Ale skoro trochę kodu zostało już przeczytane, widać, że pub sprawdza rozmiar paczki przed uploadem i w razie przekroczenia dopuszczalnego rozmiaru zwraca błąd. Szybkie oględziny kodu serwera wskazują, że z ten kolei nie sprawdza rozmiaru paczki. Nowy patch na kliencie, recompile i test… BINGO – size check bypass!
Co się właściwie stało?
Z założenia paczka powinna być mniejsza niż 100MB. Problem polegał na tym, że tylko klient sprawdzał rozmiar paczki. Kiedy kod klienta został zmieniony, żeby nie sprawdzał rozmiaru, paczka została po prostu przyjęta przez serwer.
Przykładem dlaczego jest to niebezpieczne, może być scenariusz z dołączeniem 10TB pliku z zerami do paczki. Zera zostaną łatwo skompresowane do malutkich rozmiarów, jednak po rozpakowaniu mogą zapełnić całą przestrzeń dyskową ofiary (zakładając, że ofiara nie używa systemu plików ZFS lub podobnych).
Dodatkowy scenariusz 5
Jeśli użytkownik uzna, że wykonanie rm -fr / jest dobrym pomysłem i pliki zostaną usunięte, wina nie leży po stronie programistów coreutils.
Jak właściwie działa pobieranie paczek i zależności z pub.dev? Przy pobieraniu paczki foo wykonywane jest zapytanie GET i czytany jest zwrócony w odpowiedzi json. Następnie znajdowana jest najwyższa wersja paczki, która satysfakcjonuje zdefiniowane zależności. Znając wersję, można pobrać właściwą paczkę z serwera za pomocą kolejnego zapytania GET, choć tak naprawdę paczka jest przechowywana na stałe jako tar.gz w gcs (google cloud storage). Ponieważ wszystkie paczki są publiczne, to i gcs bucket oferuje ogólnodostępny odczyt.
Skoro wszystkie paczki są dostępne, nic nie stoi na przeszkodzie, aby je pobrać na lokalny dysk:
gsutil rsync gs://${SRC_BUCKET} ${DST_DIR}
Kilkaset giga i kilka dni później mamy dostęp do wszystkich paczek w postaci tar.gz.
A co jest w tych paczkach? tar –tvf podpowiada. Po wylistowaniu zawartości archiwum mamy dostęp do listy plików wewnątrz nich. Chociaż większość kluczy ssh czy certyfikatów jest w katalogach test, to jednak kilka osób przez przypadek wrzuciło więcej niż powinno…
Na szczęście deweloperzy darta załatwili już ten problem (więcej o tym: tutaj).
Podsumowanie
Błędy opisane w scenariuszach 1-4 zostały zgłoszone i uznane przez Google bug-bounty, natomiast błąd ze scenariusza 5 został zgłoszony poprzez e-mail. W ramach programu bug-bounty przyznano 2 x 500 USD i 2 x 100 USD.
Chociaż pierwotna idea ataku na pub.dev zakładała tyko użycie yamlbomy i przemycenie niebezpiecznej wartości dla pola ‘homepage’, to wraz ze zwiększaniem znajomości systemu i znajomością kodu źródłowego pojawiały się nowe pomysły. Wraz ze wzrostem zrozumienia implementacji idzie wzrost znajomości szczegółów, a przecież wiadomo gdzie siedzi diabeł.
Sumarycznie, możliwość hakowania usług Googla okazała się bardzo ciekawym doświadczeniem a jeszcze ciekawsze było obserwowanie, jak błędy naprawiane są przez programistów darta.
Marcin Niemira <n0npax>
2x po 500 usd, chyba wyslali bo im sie czytac nie chcialo xD
To dużo czy mało? Pytam bo jestem zupełnie zielony w temacie
Zgodnie ze stawkami :)
Wszystkie stawki są dostępne tutaj: https://bughunters.google.com/about/rules/6625378258649088
pub.dev jest w najniższej kategorii w jako „other sandboxed or lower priority applications”