Przejdź do treści

Cross-Site Scripting (XSS)

XSS to podatność aplikacji webowej umożliwiająca atakującemu wstrzyknięcie danych, które w określonym kontekście mogą zostać zinterpretowane jako kod wykonywany w przeglądarce użytkownika.

Podatność XSS powstaje najczęściej, gdy niezaufane dane trafiają ze źródła, np. parametru URL lub formularza, do miejsca, w którym przeglądarka może potraktować je jako fragment kodu napisanego w języku HTML lub JavaScript. Ochrona musi być dostosowana do miejsca użycia danych. Przykładowo aplikacja może pobierać dane od użytkownika, a następnie umieszczać je bez odpowiedniego kodowania lub sanityzacji w kodzie HTML, atrybucie HTML, kodzie JavaScript lub innym kontekście, w którym mogą zostać zinterpretowane jako kod.

Ważna informacja

Sama akceptacja lub zapisanie niezaufanych danych przez aplikację nie jest jeszcze podatnością XSS. Do wykonania dochodzi dopiero w momencie, gdy te dane trafiają z powrotem do przeglądarki i zostają przez nią zinterpretowane jako kod, a nie zwykły tekst.

Skutki udanego ataku

Udany atak XSS może prowadzić do:

  • wykonywania operacji w kontekście aktywnej sesji użytkownika,
  • odczytu lub kradzieży danych dostępnych dla kodu JavaScript,
  • przejęcia konta, jeżeli aplikacja umożliwia zmianę danych uwierzytelniających bez dodatkowego potwierdzenia,
  • wyświetlania fałszywych formularzy i wyłudzania danych,
  • modyfikowania zawartości strony,
  • przekierowywania użytkownika do złośliwych stron.

Reflected XSS

Reflected XSS to rodzaj podatności XSS, w której złośliwy kod jest przesyłany w żądaniu użytkownika (najczęściej poprzez adres URL), następnie zostaje „odbity” przez serwer w odpowiedzi HTTP, a na końcu wykonany w przeglądarce ofiary. Dane nie są trwale przechowywane przez aplikację, dlatego atak zazwyczaj wymaga nakłonienia ofiary do otwarcia przygotowanego odnośnika lub wysłania określonego żądania.

Typowymi miejscami występowania podatności Reflected XSS są funkcjonalności wykorzystujące dane dostarczane przez użytkownika, takie jak wyszukiwarki, formularze, komunikaty o błędach, strony logowania, parametry URL oraz mechanizmy filtrowania i sortowania wyników.

Przykładem może być funkcjonalność wyszukiwania na stronie internetowej - użytkownik korzysta z wyszukiwarki:

https://example.com/search?q=wyszukaj

Aplikacja wyświetla wpisaną wartość:

Wyniki dla: wyszukaj

Atakujący może zmodyfikować parametr q i zamiast zwykłego tekstu przesłać kod JavaScript:

https://example.com/search?q=<script>alert(1)</script>

Jeżeli aplikacja wyświetli zawartość parametru bez odpowiedniego escapowania:

Wyniki dla: <script>alert(1)</script>

przeglądarka zinterpretuje znacznik jako kod JavaScript i wykona go. Efektem wykonania będzie popupu z cyfrą "1".

Stored XSS (Persistent XSS)

Stored XSS, nazywany również Persistent XSS, to wariant podatności, w którym niezaufane dane zostają zapisane przez aplikację, np. w bazie danych, a następnie wyświetlone bez odpowiedniego zabezpieczenia. Jeżeli przeglądarka zinterpretuje je jako aktywną treść, kod może zostać wykonany u użytkowników odwiedzających podatną stronę.

Stored XSS może mieć większy zasięg niż Reflected XSS, ponieważ zapisane dane mogą być wyświetlane wielu użytkownikom. Atak nie musi wymagać otwarcia specjalnie przygotowanego odnośnika - może wystarczyć odwiedzenie strony, na której aplikacja wyświetla niezaufane dane bez odpowiedniego zabezpieczenia.

Użytkownik dodaje komentarz:

<script>alert(1)</script>

Komentarz zostaje zapisany w bazie danych. Następnie aplikacja wyświetla go na stronie:

Komentarz: <script>alert(1)</script>

Przeglądarka interpretuje tag jako kod JavaScript i wykonuje go. Efektem będzie wyświetlenie popupu z cyfrą "1".

DOM XSS

DOM XSS powstaje, gdy kod JavaScript działający w przeglądarce pobiera dane z niezaufanego źródła i przekazuje je do funkcji, która interpretuje je jako HTML lub JavaScript. Do podatności może dojść bez umieszczania szkodliwej treści w odpowiedzi HTML generowanej przez serwer.

Reflected XSS i Stored XSS opisują sposób dostarczenia lub przechowywania danych, natomiast DOM XSS opisuje mechanizm przetwarzania danych po stronie przeglądarki. Kategorie te nie zawsze są całkowicie rozłączne.

Przykład podatności

Kod aplikacji (JavaScript):

document.getElementById("output").innerHTML = location.hash;

Użytkownik odwiedza stronę:

https://example.com/#test

Strona wyświetli:

<div id="output">#test</div>

Wstrzyknięcie złośliwego kodu:

https://example.com/#<img src=x onerror=alert(1)>

Po załadowaniu strony JavaScript wstawi:

<div id="output">
  #<img src=x onerror=alert(1)>
</div>

Ponieważ dane zostały przekazane bezpośrednio do innerHTML, zawarty w nich znacznik HTML może zostać zinterpretowany przez przeglądarkę, a obsługa onerror może doprowadzić do wykonania kodu JavaScript:

alert(1)

Info

DOM XSS najczęściej wynika z niebezpiecznego wykorzystania danych ze źródeł takich jak np: location.hash, location.search, document.referrer, postMessage, danych z localStorage / sessionStorage. Przykładami typowych niebezpiecznych sinków (ang. sink), czyli miejsc w kodzie JavaScript, które mogą zinterpretować przekazane dane jako HTML lub kod wykonywalny, są: innerHTML, outerHTML, document.write(), eval() oraz insertAdjacentHTML().

Rekomendacje

Aby ograniczyć ryzyko XSS, należy:

  • wyświetlać niezaufane dane jako zwykły tekst, korzystając z mechanizmów automatycznego kodowania dostępnych w używanym frameworku,
  • dostosowywać sposób zabezpieczenia do miejsca użycia danych. Dane umieszczane w zwykłym tekście HTML, atrybucie, adresie URL, CSS lub JavaScript wymagają różnych metod kodowania,
  • unikać umieszczania niezaufanych danych bezpośrednio wewnątrz skryptów, stylów CSS, obsługi zdarzeń, takich jak onclick, oraz innych miejsc, w których samo kodowanie może być niewystarczające,
  • jeżeli użytkownicy mogą wprowadzać HTML, oczyszczać go za pomocą aktualizowanej i sprawdzonej biblioteki, np. DOMPurify, oraz ograniczać dozwolone elementy i atrybuty. Nie należy modyfikować treści po jej oczyszczeniu w sposób, który może ponownie wprowadzić niebezpieczne elementy,
  • w kodzie JavaScript korzystać z funkcji traktujących dane jako tekst, takich jak textContent, oraz bezpiecznie tworzyć elementy za pomocą createElement(). Unikać przekazywania niezaufanych danych do innerHTML, outerHTML, insertAdjacentHTML(), document.write() i eval(),
  • stosować Content Security Policy (CSP) jako dodatkową warstwę ochrony, ograniczającą możliwość wykonania niepożądanego kodu w przypadku wystąpienia podatności XSS,
  • walidować dane wejściowe względem oczekiwanego formatu i zakresu, ale traktować walidację jako zabezpieczenie dodatkowe, a nie zamiennik bezpiecznego wyświetlania danych.

Zapamiętaj

Wszystkie niezaufane dane należy bezpiecznie przetwarzać w miejscu ich użycia. Sam fakt, że dane pochodzą z formularza, adresu URL, bazy danych, pamięci przeglądarki lub innego komponentu aplikacji, nie oznacza, że są bezpieczne.

Jeżeli aplikacja pozwala użytkownikowi dostarczyć dane, a następnie interpretuje je jako kod wykonywalny, istnieje ryzyko wystąpienia podatności XSS.