Młody dev zaskoczył Google’a. Zobaczcie, w jaki sposób znalazł bugi
W lutym wykrył błąd, pod koniec miesiąca zgłosił go, a kilka dni później znalazł kolejny. W połowie marca Google przyznało mu rację, ale dopiero dwa miesiące później naprawiło błąd i wypłaciło nagrodę młodemu programiście. Co Ezequiel Pereira znalazł w kodzie Google’a?
“Google nagrodziło mnie i wypłaciło 36,337 tys. dolarów!” — cieszy się Ezequiel Pereira, 18-letni początkujący programista z Urugwaju. Wykrył buga w zabezpieczeniach Google’a, o czym powiadomił właścicieli strony. Ci przeanalizowali jego odkrycie i przyznali mu rację. Zobaczcie, co dokładnie młody Ezequiel Pereira znalazł w kodzie.
“Jakiś czas temu zauważyłem, że każdy Google App Engine (GAE) zawiera w headerze “X-Cloud-Trace-Context”. Założyłem więc, że każda strona z takim headerem prawdopodobnie działa w oparciu o GAE” – zaczyna swoją opowieść na blogu. Pereira z wcześniejszego założenia wywnioskował, że strona appengine.google.com także działa w oparciu o GAE, ale wykonuje kilka akcji, które nie są dostępne dla innych użytkowników tego silnika. “Postanowiłem sprawdzić, jak mimo wszystko wykonać te akcje” – dodaje początkujący dev.
Na początku, Ezequiel zaczął uczyć się, w jaki sposób GAE wykonuje akcje, jak np. pisanie logów, czy korzystanie z tokenów OAuth. W ten sposób dowiedział się, że w środowisku Java 8 robi to wysyłając Protocol Buffer bezpośrednio do serwera HTTP zlokalizowanego tutaj: http://169.254.169.253:10001/rpc_http.
POST /rpc_http HTTP/1.1 Host: 169.254.169.253:10001 X-Google-RPC-Service-Endpoint: app-engine-apis X-Google-RPC-Service-Method: /VMRemoteAPI.CallRemoteAPI Content-Type: application/octet-stream Content-Length: <LENGTH> <PROTO_MESSAGE>
W miejscu wiadomości, Pereira wpisał „apphosting.ext.remote_api.Request”, gdzie:
- service_name = nazwa API do wywołania
- method = nazwa metody API do wywołania
- request = wewnętrzne żądanie PB (w formacie binarnym)
- request_id = ticket bezpieczeństwa (podawany na każde żądanie GAE), wymagany, choć opisany jako opcjonalny
Jeśli chodzi o ticket bezpieczeństwa, to Pereira podaje taki przykład:
import com.google.apphosting.api.ApiProxy; import java.lang.reflect.Method; Method getSecurityTicket = ApiProxy.getCurrentEnvironment().getClass().getDeclaredMethod("getSecurityTicket"); getSecurityTicket.setAccessible(true); String security_ticket = (String) getSecurityTicket.invoke(ApiProxy.getCurrentEnvironment());
Biorąc pod uwagę powyższe odkrycie, jeśli Pereira chciałby uzyskać dostęp do OAuth tokena w zakresie testowym, wykonałby te kroki:
1. Wygeneruj „apphosting.GetAccessTokenRequest” z wiadomością:
scope =
[„https://www.googleapis.com/auth/xapi.
2. Wygeneruj „apphosting.ext.remote_api.Request” z wiadomością:
service_name = „app_identity_service”
(API da ci dostęp do GAE Service Account)
method = „GetAccessTokenRequest”
request = wiadomość PB z poprzedniego kroku, zakodowana w formacie binarnym
request_id = ticket bezpieczeństwa
3. Wyślij zapytanie do HTTP
4. Odpowiedź będzie brzmiała „apphosting.GetAccessTokenResponse”
Wszystko to utwierdziło młodego deva w przekonaniu, że “appengine.google.com” wykonuje akcje, które trudno wykonać innemu użytkownikowi, ale nadal nie wiedział, co mogłoby być tymi akcjami. Pomyślał, że appengine.google.com może używa po prostu innego serwera. Sprawdził to i dowiedział się, że czwarty port serwera jest otwarty, więc postanowił się do niego dostać. W odpowiedzi na wysłane polecenia otrzymał “dziwny zbiór danych”, ale po przyjrzeniu się mu zrozumiał, że to gRPC serwis.
“Próbowałem w Javie stworzyć klienta gRPC, który będzie działał w oparciu o GAE, ale miałem z tym spore problemy, ponieważ biblioteka wydawała się niepełna” – pisze Ezequiel Pereira. Mimo kolejnych problemów, nie poddał się i stworzył klienta w C++. Z tego testu dowiedział się tylko, że API “apphosting.APIHost” ma opcję na JSON. “Ze względu na to, że nic innego się nie dowiedziałem, założyłem, że akcje GAE wewnętrznie kontaktują się z innym serwerem lub używają usług RPC (HTTP / gRPC) do wywoływania ukrytych API / metod” – dodaje. Kolejne założenia Ezequiela brzmiało: GAE musi używać ukrytego API. Nie wiedział jednak, jak je znaleźć.
Przyjrzał się więc bliżej Protocol Buffer w poszukiwaniu jakiegokolwiek śladu ukrytego API. Znalazł obiecujący plik “apphosting/base/appmaster.proto” oraz API nazwane “AppMaster”, ale po kilku testach okazało się, że to nie to, czego od początku szuka. Zaczął więc przyglądać się bliżej plikom binarnym. “Były ogromne i pełne niezrozumiałych dla mnie komend, ale po zauważeniu, że “java_runtime_launcher_ex” ma sporo komend, wpadłem na pomysł, żeby przyjrzeć im się bliżej.
Na początku było to bardzo trudne, ale wymyśliłem, że stworzę bibliotekę Java w C++ z metodą, która odczytującą argumenty przekazane do programu. Wykonanie tego było bardzo proste, a to dzięki społeczności StackOverflow, która rozmawiała wcześniej o tym problemie. Wystarczyło wykorzystać poniższe kilka linijek.
int argc = -1; char **argv = NULL; static void getArgs(int _argc, char **_argv, char **_env) { argc = _argc; argv = _argv; } __attribute__((section(".init_array"))) static void *ctr = (void*) getArgs;
Oto co zobaczył Pereira po wpisaniu powyższej komendy:
--api_call_deadline_map= app_config_service:60.0, blobstore:15.0, datastore_v3:60.0, datastore_v4:60.0, file:30.0, images:30.0, logservice:60.0, modules:60.0, rdbms:60.0, remote_socket:60.0, search:10.0, stubby:10.0
Wynik dał Ezequielowi do myślenia. Zrozumiał z niego, że “longservice” to interfejsy API dostępne za pośrednictwem wewnętrznego punktu końcowego HTTP. “Zauważyłem także, że “stubby” było infrastrukturą RPC, dzięki której “appengine.google.com” wykonuje wewnętrze akcje” – pisze na blogu. Poznał więc nazwę wewnętrznego API, ale nie wiedział jakie metody wykorzystuje.
Próbował wywołać kilka z nich, ale za każdym razem otrzymywał komunikat o błędzie. Zaczął więc szukać informacji o nim w sieci i znalazł post z 2010 roku z taką wiadomością: The API call stubby.Send() took too long to respond and was cancelled.
Wpisał więc metodę “Send”, ale i tak nie podziałała. “Byłem pewny, że musi istnieć, więc komunikat pewnie starał się zakryć informację o tym, że istnieje” — napisał na blogu. Szukał więc różnic między komunikatami o błędach. Znalazł je wysyłając request “apphosting.APIRequest.pb” z klienta gRPC. Zobaczył komunikat o błędzie dotyczącym nieistniejącej metody. “Jak jednak wykorzystać te informacje?” – zastanawiał się młody programista.
Przypomniał sobie, że w ramach akcji Google, której celem było wykrycie błędów bezpieczeństwa, otrzymał dostęp do staging-appengne.sandbox.googleapis.com oraz do test-appengine.sandbox.googleapis.com. Po małym researchu, postanowił wykonać te kroki.
1. Pobrać wersję z manualnym skalowaniem
2. Zmienić header “Host” na “<PROJECT-NAME>.prom-<qa/nightly>.sandbox.google.com”.
3. Jeśli aplikacja działa na “save-the-expanse.appspot.com”, powinieneś zmienić <PROJECT-NAME> na “save-the-expanse”. Tak samo zmień <qa/nightly> na “qa”. Dla przykładu, Pereira przetestował “the-expanse.prom-nightly.sandbox.google.com”.
Błąd nr 1
“Kiedy opublikowałem aplikację klienta gRPC, odkryłem, że w środowisku nie-produkcyjnym GAE [staging/test, do którego dostał dostęp, ponieważ brał udział w programie Google’a], mam dostęp do “stubby.Send!” – cieszy się Ezequiel Pereira. “Po szybkich testach (polegających głównie na czytaniu komunikatów o błędach i próbowaniu naprawienia ich) znalazłem sposób na proste wywołanie Subby” – pisze na blogu.
1. Wywołaj “stubby.GetSubId” za pomocą wiadomości JSON PB:
{ "host": "<HOST>" }
W <HOST> ustaw jaką metodę chcesz wywołać (np. “google.com:80”, “pantheon.corp.google.com:80”, “blade:monarch-cloud_prod-streamz”). Wydaje się, że “blade:<SERVICE>” jest wewnętrznym DNSem dla użytkowników Google.
2. Poprzednie żądanie zwróci nam wiadomość JSON PB “stub_id”.
3. Wywołaj “stubby.Send” z następującą wiadomością JSON PB:
{ "stubby_method": "/<SERVICE>.<METHOD>", "stubby_request": "<PB>", "stub_id": "<STUB_ID>" }
Żeby sprawdzić jakie wartości kryją się za “stubby_method”, ustaw “/ServerStatus.GetServices” z pustym “stubby_request”. Ujrzysz wtedy “rpc.ServiceList” z listą wszystkich serwisów i metod.
4. Jeśli wpisałeś wszystko poprawnie, powinieneś zobaczyć wiadomość JSON PB “stubby_response”.
“Po odkryciu tego, wykonałem kilka testów, ale nie znalazłem niczego groźnego. Mimo to, zgłosiłem lukę Google’owi, a ten nadał jej priorytet P1” — pisze Pereira. Po zgłoszeniu, Ezequiel przejrzał jeszcze raz wszystko, co mogłoby narazić bezpieczeństwo Google’a. Znalazł kolejny błąd.
Błąd nr 2
Pereira odkrył drugie ukryte API. Przeszukując definicje PB natknął się na log “app_config_service”. Później znalazł “apphosting/base/quotas.proto”. Po kilku próbach znalazł “APP_CONFIG_SERVICE_GET_APP_CONFIG”, które okazało się ukrytą metodą “app_config_service.GetAppConfig”. Młody dev szukał dalej luki i znalazł m.in. “app_config_service.ConfigApp” oraz “app_config_service.SetAdminConfig”. To odkrycie także zaraportował Google’owi, który przyjął zgłoszenie, ale zablokował Ezequeielowi dostęp do API.
Po kilku dniach przedstawiciel Google odezwał się z podziękowaniem za wykrycie bugów i z informacją o nagrodzie za poświęcony czas na ich znalezienie. Poziom wynagrodzenia uzależniony jest od stopnia niebezpieczeństwa wykrytych bugów (w ubiegłym roku jeden z devów w nagrodę dostał 112 tys. dolarów). Pereira nie pierwszy raz dostał kasę od Google’a w ramach akcji szukania błędów. Rok temu, w wieku 17-lat, dostał 10 tys. dolarów. Nudził się w szkole i postanowił poszukać bugów w kodzie Google’a. Opłacało się, a zdobyte wtedy środki przeznaczył na sfinansowanie nauki w amerykańskiej szkole.