Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
BetterZip – czyli od XSS-a do wykonywania dowolnego kodu
XSS (Cross-Site Scripting) to jedna z najbardziej popularnych podatności świata aplikacji webowych. Na liście OWASP TOP10 niezmiennie od wielu lat zajmuje pierwsze miejsce pod względem powszechności. Do tej pory XSS-y były zwykle utożsamiane wyłącznie ze światem przeglądarek, jednak ze względu na fakt, że HTML i JavaScript ostatnio coraz mocniej przenikają do świata aplikacji desktopowych (np. framework Electron) i mobilnych (Cordova), skutki XSS-ów mogą być poważniejsze niż kiedykolwiek wcześniej. W tym artykule zobaczymy na przykładzie aplikacji na systemy macOS – BetterZip – jak XSS może posłużyć do wykonania dowolnego kodu na komputerze.
Czym jest XSS
XSS (Cross-Site Scripting) to podatność świata webu, dzięki której napastnik jest w stanie wykonać swój własny skrypt JavaScript w kontekście atakowanej witryny. Najczęściej przytaczany przykład to sytuacja, w której jeden z parametrów zapytania HTTP jest odbijany bezpośrednio w kodzie HTML. Najprostszym przykładem jest wykonanie zapytania pod adres: /search.php?query=<script>alert(1)</script> , by w odpowiedzi otrzymać:
<div>Nie znaleziono wyników dla <strong><script>alert(1)</script></strong></div>
Zwykle w przykładach XSS-ów pokazuje się właśnie wykonanie kodu alert(1), choć oczywiście wyświetlenie komunikatu w JavaScript nie powoduje żadnych poważnych konsekwencji. Jest to jednak wystarczający dowód na to, że istnieje możliwość wykonania własnego kodu JS. Jak więc XSS wykorzystać w rzeczywistości? Istnieje kilka podstawowych skutków:
- Możliwość kradzieży ciasteczek sesyjnych, a w konsekwencji uzyskanie dostępu do sesji atakowanego użytkownika.
- Możliwość odczytu dowolnych danych w kontekście zaatakowanej domeny. Na przykład: XSS w Gmailu może pozwolić na odczyt wszystkich maili użytkownika.
- Możliwość wykonywania dowolnych akcji w kontekście zaatakowanej domeny. Na przykład: XSS w Facebooku może pozwolić na polubienie lub dzielenie się dowolnymi stronami.
- Ataki na sieć lub komputer użytkownika – np. z wykorzystaniem exploitów na przeglądarki lub przeprowadzenie podstawowego skanowania portów.
W dalszej części tekstu skupimy się na tym trzecim skutku, tj. możliwości wykonywania dowolnych akcji w kontekście atakowanej domeny.
BetterZip i QuickLook
BetterZip jest aplikacją do podglądu i tworzenia archiwów (7z, rar, zip itd.) na system macOS. W domyślnej instalacji BetterZip podpina się też pod funkcję QuickLook (szybki podgląd) w systemie, dzięki której naciśnięcie spacji w domyślnej przeglądarce plików na macOS-ie powoduje wyświetlenie podglądu tego pliku. W przypadku użycia QuickLook na plikach zip wyświetlana jest ich zawartość (Rysunek 1).
Jak widać na Rysunku 1, fragment nazwy jednego z plików jest podkreślony. Zauważyłem to kiedyś przypadkowo, gdy uruchomiłem QuickLook na jednym z plików, które miałem na dysku. Sprawdziłem następnie, jakie są rzeczywiste nazwy plików wewnątrz tego archiwum, i wyszło na to, że nazwa tego „podkreślonego” pliku to: something<u>other.jpg. Prowadzi to zatem do wniosku, że w QuickLooku istnieje możliwość wstrzyknięcia własnego kodu HTML.
Próba wykonania kodu JavaScript
Skoro wiemy już, że możemy wstrzyknąć własny kod HTML, naturalnym następnym krokiem jest chęć wstrzyknięcia własnego kodu JavaScript. Pierwszą myślą jest zatem dodanie najbardziej standardowego wstrzyknięcia, tj. <script>alert(1)</script>. Nie możemy jednak go użyć, gdyż zawiera ono w sobie znak slasha („/”), którego nie można użyć w nazwie pliku; znak ten bowiem jest separatorem folderów w ścieżce.
Koniecznym jest zatem użycie kodu HTML bez slasha, co przywodzi na myśl inne standardowe wstrzyknięcie kodu JS: <img src=1 onerror=alert(1)>. Tok działania przeglądarek HTML jest następujący:
- Przeglądarka próbuje pobrać plik o nazwie „1” (wartość atrybutu src),
- Z dużym prawdopodobieństwem taki plik nie będzie istniał lub nie będzie obrazkiem…
- …wówczas przeglądarka wyzwala zdarzenie onerror, a co za tym idzie, wywołuje kod alert(1).
Przygotowałem zatem plik o nazwie <img src=1 onerror=alert(1)>, spakowałem go do archiwum zip i spróbowałem otworzyć QuickLook. Obrazek wyraźnie się pojawił (Rysunek 2), nie wykonał się jednak żaden alert.
Takie zachowanie mogło wynikać z jednej z dwóch podstawowych przyczyn:
- W QuickLooku można korzystać z kodu HTML, jednak nie jest pod niego podpięty żaden silnik JS, stąd nie ma możliwości wykonywania kodu,
- Kod JS się rzeczywiście wykonał, ale w kontekście, w którym jest wykonywany, nie jest zdefiniowana funkcja alert.
Ten drugi wariant wydał mi się całkiem prawdopodobny, postanowiłem więc skorzystać z innego kodu JS. Zamiast próbować wywołać alert, mogę po prostu spróbować podmienić pełną treść strony. Z poziomu JS można to zrobić, odwołując się do elementu document.body i jego właściwości innerHTML. Zatem nowa nazwa pliku to: <img src=1 onerror=document.body.innerHTML=’AAAAAA’>. W wyniku wykonania tego kodu treść strony powinna podmienić się na szereg liter „A”. Jak widać na Rysunku 3 – rzeczywiście tak się stało!
W ten sposób uzyskałem już wystarczający dowód, że mogę wykonywać swój kod JavaScript w QuickLooku. Do rozwiązania pozostała ostatnia, najważniejsza kwestia: co ciekawego mogę z poziomu tego kodu JS zrobić?
„Zainstaluj aplikację dla użytkownika i uruchom”
Okazuje się, że BetterZip udostępnia z poziomu QuickLooka kilka opcji do szybkiego rozpakowania archiwum. Widoczne są one na Rysunku 4.
Najciekawiej wygląda ta trzecia opcja: „Zainstaluj aplikację dla użytkownika i uruchom”. Po jej wybraniu BetterZip rozpakowuje plik archiwalny do katalogu ~/Applications i, jeżeli w środku znajduje się plik wykonywalny, automatycznie go uruchamia!
Użytkownik może więc „wyklikać” sobie tę opcję z menu, a my, jako napastnicy, będziemy chcieli automatycznie „wyklikać” ją z poziomu JavaScriptu. Żeby nieco ułatwić sobie pracę, wcześniej warto pobrać pełne źródła HTML tej strony z QuickLookiem. Aby to osiągnąć, wystarczyło pobrać zawartość elementu document.documentElement (odpowiada on głównemu elementowi <html> w dokumencie) w następujący sposób: <img src=1 onerror=document.documentElement.textContent=document.documentElement.outerHTML>. Dzięki temu treść strony zostanie zastąpiona jej pełnym HTML-em, który następnie można skopiować sobie do schowka i analizować.
Przyjrzyjmy się, jak wygląda fragment kodu HTML, w którym zdefiniowano element
<select>:
<a href="#" id="presetLink" name="presetLink"> <select name="preset" onchange="extractWithPreset(this);"> <option disabled="disabled" selected="selected"> Extract with preset </option> <option value="1-Klik%20wypakuj"> 1-Klik wypakuj </option> <option value="1-Klik%20wypakuj%20i%20wyczy%C5%9B%C4%87"> 1-Klik wypakuj i wyczyść </option> <option value="Zainstaluj%20aplikacj%C4%99%20dla%20u%C5%BCytkownika%20i%20uruchom"> Zainstaluj aplikację dla użytkownika i uruchom </option> </select> </a>
Przeanalizujmy zatem, co się dzieje, gdy użytkownik z poziomu interfejsu klika na opcję „Zainstaluj aplikację dla użytkownika i uruchom”. Przede wszystkim ustawiany jest indeks wybranego elementu <option> na 3 (jest to czwarty kolejny element <option> z indeksowaniem od zera), następnie wyzwalane jest zdarzenie onchange, w którym z kolei wywoływana jest funkcja extractWithPreset (ta funkcja po prostu zmienia atrybut href linka nadrzędnego), dla której argumentem jest element <select>. Na końcu, jako że element <select> jest zagnieżdżony w elemencie <a>, wyzwalane zostaje kliknięcie na linka z elementu <a>.
Z poziomu JavaScript zrobimy zatem dokładnie to samo:
- Ustawimy indeks wybranego elementu na 3,
- Wywołamy funkcję extractWithPreset,
- Zasymulujemy kliknięcie linka.
Z poziomu kodu JS wygląda to następująco:
// Uzyskujemy referencję do elementu <select> var select = document.querySelector('select'); // Wybieramy czwarty element select.selectedIndex = 3; // Wywołujemy funkcję extractWithPreset extractWithPreset(select); // "Symulujemy" kliknięcie linka o id presetLink document.querySelector('#presetLink').click();
Zatem by przeprowadzić atak, musimy przygotować plik o rozszerzeniu .command (to macOS-owy odpowiednik plików .sh), w którym możemy wykonać dowolną złośliwą operację na komputerze użytkownika i nadać temu plikowi nazwę, która będzie zawierała kod javascriptowy. Może wyglądać tak: <img src=1 onerror=”select=document.querySelector(’select’); select.selectedIndex=3;extractWithPreset(select); document.querySelector(’#presetLink’).click();”> . Następnie plik musimy spakować do zipa, podesłać do ofiary… i liczyć, że uruchomi na tym pliku QuickLooka :).
Efekt może być taki jak na Rysunku 5.
Podsumowanie
W artykule pokazano na przykładzie BetterZipa, w jaki sposób podatność XSS, kojarzona zazwyczaj z aplikacjami webowymi, przenika do aplikacji desktopowych. W tym przypadku mogła zostać wykorzystana do wykonywania dowolnego kodu na komputerze ofiary. Ze względu na rosnącą popularność frameworków typu Electron można się spodziewać, że tego typu ataki w najbliższych latach będą coraz popularniejsze.
Błąd w BetterZipie został zgłoszony do autora 25 czerwca 2016; zaś został naprawiony w wersji z 14 lipca 2016. W nagrodę za jego znalezienie autor podarował mi darmową licencję ;-)
Michał Bentkowski, realizuje testy penetracyjne w Securitum.
Bardzo fajny artykuł. Ja korzystam z keka :)
Bardzo trafne i interesujące spostrzeżenia Uświadomiłeś mi całą gamę nowych problemów bezpieczeństwa, o której wcześniej jakoś nie myślałem. Tylko więcej takich artykułów! Dzięki!
inspirujące, brawo ;)