Juniors, News

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.

Redaktor naczelny w Just Geek IT

Od pięciu lat rozwija jeden z największych polskich portali contentowych dot. branży IT. Jest autorem formatu devdebat, w którym zderza opinie kilku ekspertów na temat wybranego zagadnienia. Od 10 lat pracuje zdalnie.

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://geek.justjoin.it/mlody-dev-zaskoczyl-googlea-zobaczcie-sposob-znalazl-bugi/" order_type="social" width="100%" count_of_comments="8" ]