Witajcie po małej przerwie spowodowanej natłokiem zadań związanych ze zmianą pracy, pragnieniu wypoczynku i planowaniu urlopu. Nazbierało się trochę tego wszystkiego i nie miałem głowy do kolejnego wpisu. Wybaczcie.

W związku z tym, że obiecałem Wam przykłady do artykułu o CSP, w niniejszym wpisie takie się znajdą. CSP to linia obrony przed kilkoma typami ataków na strony internetowe, więc zachęcam do sukcesywnego wdrażania jej w swoje projekty. Na pewno nic nie stracicie oprócz naprawdę kilku minut na dodanie ochrony i następnie konserwację zabezpieczenia.

Poniżej zaprezentuję zachowanie strony przy zastosowaniu określonych dyrektyw CSP 2.0. Pokażę wynik próby łamania zabezpieczenia (poprzez osoby z zewnątrz lub poprzez nieumyślne niedostosowanie źródeł dyrektywy).

Przykłady będę obrazować na wygenerowanym szablonie projektu opartego o Laravel 5.

1. base-uri

Kod strony:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 
 <title>Project 1</title>

 <link href='https://fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
 <link href='style.css' rel='stylesheet' type='text/css'>
 
 <base href="http://localhost/images/"></base>
 </head>
 <body>
 <div class="container"><img src="laravel_5.jpg" /></div>
 <div class="container">
 <div class="content">
 <div class="title">Laravel 5.2.23 - brodząc w chmurach</div>
 </div>
 </div>
 </body>
</html>

Zwróćcie proszę uwagę na znacznik <base> w nagłówku i dodane zdjęcie ze źródłem, jako adres względny. Wszystko wygląda elegancko, ale czy nie ułatwiamy w ten sposób manipulacji zasobami (zwłaszcza, gdy większość lub wszystkie określamy adresami względnymi)? Owszem. Wystarczy złośliwy monkey script w naszej przeglądarce i możemy zostać „zasypani” zasobami z zewnątrz.

Pominięcie znacznika <base> i ustalenie źródła obrazka na „images/laravel_5.jpg” pozwoli osiągnąć ten sam cel, ale tak czy inaczej <base> można dodać w trakcie wykonywania skryptów.

Efekt działania strony poniżej:

CSP base-uri - efekt z wyłączoną dyrektywą

CSP base-uri – efekt z wyłączoną dyrektywą

Taki sam efekt daje ustawienie dyrektywy CSP na źródło ‚self’, gdyż pozwalamy na ustawienie bazowego źródła na domenę własną zgodnie z zasadą same origin policy.

header("Content-Security-Policy: base-uri 'self'");

Jeśli jednak zmienimy w tym przypadku źródło na ‚none’ – brak przyzwolenia na jakiekolwiek zmiany, otrzymamy taki rezultat (proszę spojrzeć na ostrzeżenie w konsoli JavaScript u dołu obrazka):

CSP base-uri - efekt z włączoną dyrektywą (źródło 'none')

CSP base-uri – efekt z włączoną dyrektywą (źródło ‚none’)

Manipulacja źródłem w tym przypadku, jak i następnych przykładam należy do zadania programisty i zależy od potrzeb.

 

2. child-src

Kod strony:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 
 <title>Project 1</title>

 <link href='https://fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
 <link href='style.css' rel='stylesheet' type='text/css'>
 </head>
 <body>
 <div class="container"><iframe src="http://localhost" style="width: 400px; height: 400px;"></iframe></div>
 <div class="container">
 <div class="content">
 <div class="title">Laravel 5.2.23 - brodząc w chmurach</div>
 </div>
 </div>
 </body>
</html>

Tutaj na potrzeby dyrektywy występuje znacznik <iframe>.

Rezultat:

CSP child-src - efekt z wyłączoną dyrektywą

CSP child-src – efekt z wyłączoną dyrektywą

Ustalenie dyrektywy na:

header("Content-Security-Policy: child-src 'none'");

spowoduje wyświetlenie komunikatu w konsoli i zablokowanie treści ramki.

CSP child-src - efekt z włączoną dyrektywą (źródło 'none')

CSP child-src – efekt z włączoną dyrektywą (źródło ‚none’)

 

3. connect-src

Kod strony:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 
 <title>Project 1</title>

 <link href='https://fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
 <link href='style.css' rel='stylesheet' type='text/css'>
 
 <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
 
 <script>$.get('http://localhost/version', function(data) { $('.title>span').text(' '+data); });</script>
 </head>
 <body>
 <div class="container">
 <div class="content">
 <div class="title">Laravel<span></span> - brodząc w chmurach</div>
 </div>
 </div>
 </body>
</html>

Tutaj dodałem zewnętrzny zasób jQuery w celu łatwego poruszania się manipulowania węzłami strony (już z przyzwyczajenia wykorzystuję). Jednak nie on jest najważniejszy. Dyrektywa odpowiada za ajaxowe żądania i tutaj wykorzystuję krótki skrypt, gdzie funkcją ajaxową $.get pobieram z serwera wersję i wstrzykuję na stronę do węzła span. Przykład prosty, obrazowy:

CSP connect-src - efekt z wyłączoną dyrektywą

CSP connect-src – efekt z wyłączoną dyrektywą

Jeśli zablokujemy jak źródło żądań ajaxowych lub zmienimy wartość niepozwalającą na wywołanie adresu /version, wersja Laravela nie ukaże nam się na ekranie – żądanie zostanie zablokowane.

Dyrektywa:

header("Content-Security-Policy: connect-src 'none'");

Rezultat:

CSP connect-src - efekt z włączoną dyrektywą (źródło 'none')

CSP connect-src – efekt z włączoną dyrektywą (źródło ‚none’)

 

4. default-src

Szczególna dyrektywa. Dla kodu z poprzedniego przykładu i ustawienia wyłącznie dyrektywy default-src na ‚self’ spowoduje wyświetlenie mało obiecującego rezultatu:

CSP default-src - efekt z włączoną dyrektywą (źródło 'self')

CSP default-src – efekt z włączoną dyrektywą (źródło ‚self’)

Widzimy brak czcionki, brak wywołania $.get, co nie powinno nas dziwić – w końcu nie ustawiliśmy pozwolenia na skrypt inline, a także do doładowanie czcionki z serwerów google (oraz biblioteki jQuery z CDN). Natomiast style CSS zostały doładowane bez problemu z pliku style.css. Aby wszystko ładnie wyglądało należałoby dodać dyrektywę font-srcscript-src (by umożliwić doładowanie zasobów i wywołanie skryptu inline). Rozwiązanie tego przykładu znajdziesz w następnych częściach artykułu.

 

5. font-src

Tutaj również wykorzystamy kod z przykładu 3. Gwoli wyjaśnienia już tutaj dyrektywa default-src NIE będzie wykorzystywana.

Poniżej efekt dla dyrektywy:

header("Content-Security-Policy: font-src 'self'");
CSP font-src - efekt z włączoną dyrektywą (źródło 'self')

CSP font-src – efekt z włączoną dyrektywą (źródło ‚self’)

Wszystko w porządku oprócz czcionki. Nie ma czemu się dziwić – udostępniliśmy wyłącznie czcionki tzw. wewnętrzne.

Naprawić to możemy dodając źródło naszej czcionki:

header("Content-Security-Policy: font-src 'self' https://fonts.gstatic.com/s/lato/");

Tutaj jest akurat śliski temat, gdyż czcionkę dodajemy przez <link> do arkusza stylów, gdzie znajduje się adres do czcionki. Gdyby Google zmienił serwer, domenę lub ścieżkę do zasobu czcionka nie zostałaby doładowana na stronie z wyżej wskazanym zabezpieczeniem.

 

6. form-action

Zabezpieczenie akcji formularza, ważne gdyż chroni przez wysłaniem danych wrażliwych.

Kod strony:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 
 <title>Project 1</title>

 <link href='https://fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
 <link href='style.css' rel='stylesheet' type='text/css'>
 
 <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
 
 <script>$.get('http://localhost/version', function(data) { $('.title>span').text(' '+data); });</script>
 </head>
 <body>
 <div class="container" style="width: 600px">
 <div>FORMULARZ</div>
 <form action="/form">
 <input name="comment" type="text"></input>
 <input type="submit" name="submit" value="Wyślij"></input>
 </form>
 </div>
 <div class="container">
 <div class="content">
 <div class="title">Laravel<span></span> - brodząc w chmurach</div>
 </div>
 </div>
 </body>
</html>

Pojawia się formularz z ustawionym adresem akcji na /form. Wysyłka takiego formularza przy braku zabezpieczeń daje następujący efekt:

CSP form-action - efekt z wyłączoną dyrektywą

CSP form-action – efekt z wyłączoną dyrektywą

Natomiast, gdy uniemożliwimy wysyłkę danych z formularza (źródło ‚none’ lub ustawimy na inne niż wymaga tego atrybut action węzła form):

header("Content-Security-Policy: form-action 'none'");

otrzymamy brak reakcji na submit formularza poparty odpowiednimi alertami w konsoli JS:

CSP form-action - efekt z włączoną dyrektywą (źródło 'none')

CSP form-action – efekt z włączoną dyrektywą (źródło ‚none’)

Przeglądarka blokuje już samą inicjalizację wysyłki danych, co wygląda obiecująco, ale PAMIĘTAJ:

Wykorzystywana przez użytkownika przeglądarka może być na tyle prymitywna, że nie będzie obsługiwać zabezpieczeń CSP.

 

7. frame-ancestors

Bardzo ciekawa dyrektywa ze względu na tzw. odwrotny przepływ – w węźle zagnieżdżony następuje sprawdzanie nagłówka w poszukiwaniu źródła i jeśli strona z ramką jest „na białej liście” zasób jest renderowany w ramce, w przeciwnym wypadku jest blokowany.

Wykorzystam kod z przykładu 3. Jednak nieco go zmodyfikuję – usunięcie żądania ajaxowego, zbędnych bibliotek i skryptów. Zamiast tego w miejscu znacznika <span> dodam <iframe>.

Kod strony:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 
 <title>Project 1</title>

 <link href='https://fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
 <link href='style.css' rel='stylesheet' type='text/css'>
 
 </head>
 <body>
 <div class="container">
 <div class="content">
 <div class="title">Laravel<iframe src="/version" style="width: auto; height: 1.5em; vertical-align: middle; padding: 0px; border: 0;"></iframe> - brodząc w chmurach</div>
 </div>
 </div>
 </body>
</html>

Rezultat:

CSP frame-ancestors - efekt z wyłączoną dyrektywą

CSP frame-ancestors – efekt z wyłączoną dyrektywą

Jeśli natomiast ustawimy, że dla żądania zasobu spod adresu /version nie ma być akceptowane wyświetlanie w ramkach to efekt będzie następujący:

CSP frame-ancestors - efekt z włączoną dyrektywą (źródło 'none')

CSP frame-ancestors – efekt z włączoną dyrektywą (źródło ‚none’)

Nastąpiło zablokowanie żądania z ramki, ale dostęp do zasobu poprzez żądanie np. ajaxowe lub bezpośrenio jest możliwe:

CSP frame-ancestors - efekt z włączoną dyrektywą (źródło 'none') - efekt żądanie bezpośredniego

CSP frame-ancestors – efekt z włączoną dyrektywą (źródło ‚none’) – efekt żądanie bezpośredniego

 

8. img-src

Kod z przykładu 1. po drobnych zmianach:

<!DOCTYPE html>
<html>
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
 
 <title>Project 1</title>

 <link href='https://fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
 <link href='style.css' rel='stylesheet' type='text/css'>
 
 </head>
 <body>
 <div class="container"><img src="/images/laravel_5.jpg" /></div>
 <div class="container">
 <div class="content">
 <div class="title">Laravel 5.2.23 - brodząc w chmurach</div>
 </div>
 </div>
 </body>
</html>

Rezultat:

CSP img-src - efekt z wyłączoną dyrektywą

CSP img-src – efekt z wyłączoną dyrektywą

Jeśli ustalimy źródło dyrektywny na katalog /img, a nie /images możemy się domyślić, że obrazek się nie wyświetli.

Dyrektywa:

header("Content-Security-Policy: img-src http://localhost/img/");

 

UWAGA:

Wskazane źródło – określenie konkretnego katalogu zasobu (przynajmniej to) nie działa poprawnie w przeglądarce Opera 37.0.2178.43. Źródło wygląda jakby nie było poprawnie walidowane i przeglądarka uznaje je jako niepoprawne – tym samym dyrektywa jest ignorowana obrazek zostaje wyświetlony. Przeglądarka Chrome 50.0.2661.102 reaguje zgodnie z założeniami.

 

Rezultat Opera:

CSP img-src - efekt z włączoną dyrektywą (źródło http://localhost/img/)

CSP img-src – efekt z włączoną dyrektywą (źródło http://localhost/img/) – Opera

Rezultat Chrome:

CSP img-src - efekt z włączoną dyrektywą (źródło http://localhost/img/) - Chrome

CSP img-src – efekt z włączoną dyrektywą (źródło http://localhost/img/) – Chrome

 

9. script-src

Znowu skorzystamy w całości z kodu z przykładu 3. Ustawienie źródła dyrektywy na ‚self’ da nam podobny rezultat jak w przykładzie 4. z ustawioną dyrektywą default-src na ‚self’ – jedyną różnicą będzie doładowana czcionka z serwerów Google. Nie zgadzamy się na zewnętrzne skrypty i skrypty inline – rezultat:

CSP script-src - efekt z włączoną dyrektywą (źródło 'self')

CSP script-src – efekt z włączoną dyrektywą (źródło ‚self’)

Komunikat w konsoli mówi jasno o możliwości włączenia skryptu inline, a właściwie trzech możliwościach: unsafe-inline, z wykorzystaniem hasha lub prefiksa nonce-.

Konsekwentnie nie zalecam stosowanie ani unsafe-inline w źródle, ani unsafe-eval, gdyż bardzo obniżają zabezpieczenie przed XSS.

Poprawna dyrektywa w tym przykładzie wygląda tak:

header("Content-Security-Policy: script-src 'self' 'sha256-OoqMnsRI8uSyHmWVsgnin9QjWb++7azPDSAbJHAZ5C0=' https://code.jquery.com/jquery-2.2.4.min.js");

Rezultat:

CSP script-src - efekt z włączoną dyrektywą (źródło 'self' i inne)

CSP script-src – efekt z włączoną dyrektywą (źródło ‚self’ i inne)

Tutaj mimo poprawnego działania otrzymujemy błąd w konsoli. Uspokajam – jest on związany w włączoną w przeglądarce wtyczką Violentmonkey, która pozwala manipulować skryptami własnymi podczas ładowania stron – tzw. monkey scripts.

Jak widać dyrektywa ma jeszcze jeden pozytywny skutek, mianowicie blokuje manipulację stroną poprzez takie skrypty, co może się przydawać, gdy projekt np. bazuje na silniku klienta i wszelkie manipulacje użytkownika są z punktu widzenia biznesowego projektu niepożądane.

 

10. style-src

Bazujmy znowu na kodzie z przykładu 3. Dla dyrektywy ustawmy źródło ‚none’. Efekt poniżej:

CSP style-src - efekt z włączoną dyrektywą (źródło 'none')

CSP style-src – efekt z włączoną dyrektywą (źródło ‚none’)

Ok, konsola mówi nam, co jest nie tak, jakie style zostały zablokowane. Zmiana na źródło ‚self’ nie przywróci nam czcionki, która jest ładowana wraz ze stylami Google.

Zmieniamy dyrektywę na:

header("Content-Security-Policy: style-src 'self' https://fonts.googleapis.com/css?family=Lato:100");

Rezultat:

CSP style-src - efekt z włączoną dyrektywą (źródło 'self' https://fonts.googleapis.com/css?family=Lato:100")

CSP style-src – efekt z włączoną dyrektywą (źródło ‚self’ https://fonts.googleapis.com/css?family=Lato:100″)

Wszystko wygląda, jak należy.

 

 

Mam nadzieję, że na tych prymitywnych przykładach pokazałem Wam, co i jak w temacie CSP. Wnioski, jakie powinniście wyciągnąć z artykułu są następujące:

  • obsługa i wsparcie dyrektyw zależy od przeglądarek i ich wersji
  • nigdy nie stosujcie źródeł unsafe-inline ani unsafe-eval
  • domyślne źródła ‚self’ jest z bardzo dużym prawdopodobieństwem bezpiecznym rozwiązaniem
  • jeśli czujesz obawy przed stosowanie dyrektyw w portalu, który przed chwilą wdrożyłeś, ale chciałbyś je dodać, skoncentruj się wyłącznie na trybie raportowania

 

Osoby, które jeszcze za bardzo nie rozumieją zasady działania CSP lub poszczególnych dyrektyw przekierowuję do poprzedniego artykułu.

 

Pozdrawiam!