-15% na nową książkę sekuraka: Wprowadzenie do bezpieczeństwa IT. Przy zamówieniu podaj kod: 10000

Można było obejść funkcję „loguj przez Facebooka” – za zgłoszenie wypłacono nagrodę ~200 000 PLN)

01 marca 2020, 21:10 | Aktualności | komentarzy 11

W dzisiejszym odcinku #vulnz, mamy coś nieco bardziej skomplikowanego. Pewnie w wielu z Was kojarzy funkcję loguj się z wykorzystaniem Facebooka:

Instagram, Netflix, Spotify czy rodzimy Wykop – to tylko kilka przykładów. Jak to działa? Na początek musimy jednorazowo potwierdzić, że chcemy korzystać z tej funkcji (jesteśmy przekierowani z danego serwisu na Facebooka, gdzie potwierdzamy, że dany serwis będzie miał dostęp do naszego imienia / nazwiska i e-maila i że w ogóle chcemy używać Facebooka do logowania się w źródłowym serwisie).

W kolejnym kroku (jeśli jesteśmy zalogowani do Facebooka), po prostu klikamy „zaloguj się z wykorzystaniem Facebooka” i voila – jesteśmy zalogowani. Od strony technicznej najczęściej działa to w ten sposób: jeśli użytkownik jest zalogowany do Facebooka, przekazuje on do aplikacji tzw. access token (notabene: powinien on być zawsze weryfikowany przez aplikację – tak aby nie można było podstawić dowolnego tokenu). W skrócie: jeśli mamy prawidłowy access token ofiary – możemy najczęściej zalogować się na jej konto! (np. na Instagramie).

I właśnie do tego sprowadza się poprawiony przez Facebook błąd: atakujący w pewien sposób zwabia ofiarę do wejścia na konkretną stronę (niech to będzie nawet sekurak.pl). Na sekuraku jest zaszyty ukryty iframe, który w tle wykrada access token (pod warunkiem że ktoś jest zalogowany do FB). Teraz atakujący ma możliwość zalogowania się jako ofiara (w aplikacji która używa funkcji 'loguj się Facebookiem’).

Jak mógł wyglądać taki iframe? Np. w ten sposób:

var app_id = '124024574287414',
app_domain = 'www.instagram.com';

var exploit_url = 'https://www.facebook.com/connect/ping?client_id=' + app_id + '&redirect_uri=https%3A%2F%2Fstaticxx.facebook.com%2Fconnect%2Fxd_arbiter%2Fr%2F7SWBAvHenEn.js%3Fversion%3D44%23origin%3Dhttps%253A%252F%252F' + app_domain;

var i = document.createElement('iframe');
i.setAttribute('id', 'i');
i.setAttribute('style', 'display:none;');
i.setAttribute('src', exploit_url);
document.body.appendChild(i);

window.addEventListener('OAuth', function(FB) {
    alert(FB.data.name);
}, !1);

Zacznijmy od końca – nasz iframe wyśle (za pomocą mechanizmu postMessage) do głównego okna przeglądarki komunikat, zawierający access token (oczekujemy na ten komunikat z wykorzystaniem window.addEventListener). Mała przerwa – czym jest postMessage? Jest to jeden z mechanizmów umożliwiających tzw. komunikację cross-origin (por. tekst o CORS na sekuraku, lub jego zaktualizowana wersja w postaci rozdziału naszej książki o bezpieczeństwie aplikacji webowych). Bardziej po ludzku, całość umożliwia różnym „oknom” przeglądarki porozumiewanie się – nawet jeśli znajdują się w kompletnie różnych domenach. W naszym przypadku pewnadomena.facebook.com wyśle access token do naszej domeny (np. sekurak.pl).

W jaki sposób zmusić Facebooka do wysłania w ten sposób naszego tokena? Badacz znalazł pewien zasób (obecnie już usunięty, choć można zobaczyć jego kopię), który w pewnym uproszczeniu wykonuje takie operacje:

var frameName = window.location.href.split("#")[1];
window.parent.postMessage(frameName,"*");

Szczególnie istotna jest druga linijka. Widzicie wysłanie komunikatu postMessage do okna rodzica? (czyli w naszym przypadku okna przeglądarki, w którym umieściliśmy iframe). Bardzo ważna (i bardzo niepoprawna) jest tutaj gwiazdka. Oznacza ona, że komunikat może być wysłany do dowolnej domeny (czyli Facebook zgadza się wysłać komunikat np. do sekuraka :)

O co z kolei chodzi w linijce pierwszej? Otóż access token jest przekazywany w URL-u po znaku # (patrz flow Implicit Grant mechanizmu OAuth2.0; cały rozdział dotyczący OAuth 2.0 mamy w naszej książce), a gotowa na Facebooku linijka właśnie wyciągnie nam ten access token!

Podsumowując:

  1. Na dowolnej domenie uruchamiamy iframe ze źródłem: https://www.facebook.com/connect/ping?client_id=’ + app_id + '&redirect_uri=https%3A%2F%2Fstaticxx.facebook.com%2Fconnect%2Fxd_arbiter%2Fr%2F7SWBAvHenEn.js%3Fversion%3D44%23origin%3Dhttps%253A%252F%252F’ + app_domain
  2. Czekamy na komunikat postMessage
  3. Podsyłamy naszą stronę osobie, która korzysta z funkcji 'loguj się z wykorzystaniem konta na Facebooku) – np. do Instagrama
  4. W tle uzyskiwany jest access token (przecież ofiara jest zalogowana do Facebooka), który następnie za pomocą postMessage jest wysyłany do okna w naszej domenie (i może zostać on przesłany dalej – czy zapisany w logach po stronie serwerowej)

Za znalezisko wypłacono $55 000.

–ms

 

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



Komentarze

  1. Tomasz21

    Bardzo ciekawy sposób, na załatwienie klienta; I co najważniejsze, jest skuteczny. A tak naprawdę chodzi o to, że korzystamy z różnych ułatwień,
    Które w swoim założeniu, mają nam ułatwić życie; A w efekcie, mogą nam zaszkodzić. Reasumując; Proste czynności są bezpieczne i niezawodne.
    A ułatwienia prowadzą na manowce.
    Pozdrawiam.

    Odpowiedz
    • Masz rację, ponieważ jeśli logujemy się gdzieś i za każdym razem wpisujemy hasło, to zapamiętujemy je, a jeśli nawet ktoś zauważył nasze hasło i mamy podejrzenie, to zawsze można zmienić, a po zmianie hasła na nowe utrwala się je poprzez każdorazowe wpisywanie. W każdym razie najlepiej jest zakładać konta na email.

      Odpowiedz
  2. Nigdy nie lubiłem tej funkcji :)

    Odpowiedz
  3. Dlatego facebooka odpalam tylko w incognito

    Odpowiedz
  4. Nism0

    Mam wrażenie, że cały ten mechanizm OAuth został stworzony w jednym celu – żeby ułatwić ataki. Choć poprawnie zaimplementowany mechanizm nie jest zły sam w sobie, to ciągle wychodzą jakieś „kwiatki” w aplikacjach z OAuth w tle.

    Odpowiedz
    • Generalnie to OAuth nie jest zrobiony do tego żeby ogarniać nim uwierzytelnianie użytkowników ;) ale niektóry go do tego używają – czasem bezpiecznie czasem nie ;)

      Odpowiedz
      • Mohito

        Przecież nie o zalogowanie się chodzi w tym ataku, tylko o pozyskanie tokena, z którym można pójść sobie do połowy Graph API. To że jedna z usług może zwrócić imię/nazwisko/mail (czyli posłużyć do zalogowania) to w tym przypadku zupełnie niegroźne w porównaniu do tego ile więcej złych rzeczy można zrobić z czyimś tokenem.

        Pierwotnym źródłem tego typu podatności jest tu przepływ typu „Implicit”, który od dawna nie jest rekomendowany właśnie z powodu tego że klient za darmo dostaje token. O tym mówi się i pisze od tak dawna (np. https://tools.ietf.org/html/draft-parecki-oauth-browser-based-apps-02), że Facebookowi powinno oberwać się za ignorowanie takich ścisłych rekomendacji. Po to właśnie jest Auth Code flow, żeby klient musiał się „z serwera” przedstawić znajomością sekretu (w przypadku single page apps za to odpowiadaja challenge_code/code_verifier ale nadal jest wymaganie POST czego javascript z innej domeny nie przejdzie!).

        Szkoda że takich technicznych informacji brakuje w Waszych wpisach.

        Odpowiedz
        • Dzięki za zwrócenie uwagi.

          No chodzi pozyskanie tokena i finalnie często możliwość zalogowania się: „Leakage of the 1st party graphql tokens, it is possible to query a mutation calls to add and confirm a new phone number for account recovery.”

          Co co rekomendacji to rzeczywiście jest tego sporo:

          A w samym writeupie pojawiają się takie:

          1. Missing the “X-Frame-Options” header. (completely framable flow) – tj. „zła” strona (np. sekurak.pl może oramkować ich flow)
          2. Additionally “window.parent” which itself saves the user interaction to zero. Wasn’t needed to bother with window.open or any button onClick event. („zła” strona automatem otrzymuje token z ramki)

          Thx też za linka do ciekawego drafta.

          Odpowiedz
    • Mohito

      OAuth2 został zaprojektowany bo „społecznościowy” przemysł nie radził sobie z SAML2, który od samego początku był zaprojektowany dobrze i bezpiecznie, ale wymaga dyscypliny w poprawnym implementowaniu podpisów cyfrowych, czego w owym czasie dostawcom robić się nie chciało, musieliby dla każdej rejestrowanej aplikacji wydawać certyfikat do komunikacji

      To najprawdopodobniej wystraszyło marketoidów na tyle skutecznie, że zamiast więc wdrażać gotowy, przemysłowy standard, Twitter postanowił przeprojektować po swojemu protokół OpenID (ale ten „stary”, z adresami HTTP jako loginami) tak żeby nie używać kryptografii tylko wszystko załatwiać prostymi przekierowaniami i żądaniami miedzy serwerami. Inni szybko dołączyli, marketing miał wreszcie coś co mógł łatwo sprzedawać na slajdach „302+POST i mamy SSO, żadne tam tokeny, podpisy i certyfikaty, ha!”.

      Protokół został jednak zaprojektowany chaotycznie, do tego w wersji 2.0 dodano różne przepływy (flows), z których niektóre (Implicit) były od samego początku krytykowane jako dziurawe.

      Nikt się tym nie przejmował i wszyscy ochoczo implementowali go po stronie serwera, bo łatwo było stawiać na witrynach „Zaloguj przez …”, DUŻO łatwiej niż w SAML, ale dużo mniej bezpiecznie. Dość powiedzieć, że Google był przez lata podatny na prosty atak, w którym przekierować zwrotnie można się było na dowolną witrynę, jeśli tylko ktoś rejestrując swoją nie podał adresu punktu końcowego (co było niewymagane!)

      Istotnie, jak pokazała historia, przykładów problemow z implementacjami OAuth jest nieporównywalnie więcej, niż tych z wdrożenia SAML2.

      Odpowiedz
  5. Pawel

    Zdaje się, że problem nadal nie jest rozwiązany, bo widzę ataki na tę funkcjonalność.

    Odpowiedz
  6. Facebook nie był nigdy specjalnie dobrze zabezpieczony. Pamiętam że kiedyś gdy zapomnieliśmy hasło wystarczyło powybierać twarze znajomych ze zdjęć.

    Odpowiedz

Odpowiedz