
Event Listener to tylko warstwa pośrednicząca
Jak pisać Event Listenery by były przyjazne i nie nastręczały nam problemów? Najpierw trzeba zrozumieć czym one tak na prawdę są i jaką mają odpowiedzialność.
Nowoczesne przeglądarki posiadają wiele wbudowanych zabezpieczeń. Niektóre z nich są włączone domyślnie, a o niektórych należy poinformować przeglądarkę by ich użyła i w jaki sposób ma to zrobić. Content Security Policy jest właśnie tym drugim zabezpieczeniem. CSP – takiego skrótu będę używał dalej – używa nagłówków odpowiedzi do zdefiniowania jakie zasoby (pliki JS, CSS, obrazki, filmy, iframe itd.) mają zostać załadowane na stronie przez przeglądarkę. Dzięki temu rozwiązaniu możemy zabezpieczyć naszą aplikację przed zdecydowaną większością ataków XSS.
Nagłówek wysyłany do przeglądarki razem z odpowiedzią definiuje, jakie typy zasobów mogą zostać załadowane na stronie. Dzięki podejściu tak zwanej white list, domyślnie blokujemy wszystkie zasoby i zezwalamy na ładowanie tylko wybranych, jasno zdefiniowanych zasobów z określonych domen.
I tak na przykład, możemy zezwolić przeglądarce na załadowanie wszystkich obrazków (z każdego źródła) na stronie, oraz ograniczyć ładowanie plików CSS i JS tylko z naszej własnej domeny.
Decyzja o załadowaniu lub blokadzie danego zasobu odbywa się na zasadzie sprawdzeniu reguł z nagłówka odpowiedzi z adresem zasobu, który ma zostać załadowany. Zasób zostanie załadowany w tedy, gdy jest zgodny co do protokołu, domeny i portu. W regułach CSP podajemy te trzy wartości by powiadomić przeglądarkę o możliwości pobrania zasobu (możemy jednak pominąć port, jest on opcjonalny).
Reguły definiujemy jak adresy URL, na przykład: https://fonts.google.com
lub https://hub.domain.com:6839
.
W Internecie istnieje świetna strona poświęcona dokładnemu opisaniu wszystkich opcji konfiguracji reguł CSP. W tym artykule poświęcę czas jedynie na opisanie użycia CSP oraz profitów jakie osiągniemy, a po szczegóły odsyłam Cię do strony https://content-security-policy.com/.
Celem tego artykułu nie jest przedstawienie jak wdrożyć CSP w Twojej aplikacji, a jedynie wyjaśnienie jak działa i jak używać CSP. Dlatego też w artykule będę posiłkował się pseudokodem, który jest tożsamy z jednym z wdrożeń, które robiłem w systemie CMS firmy w której pracuję. Wygląda to następująco:
// Dodaje wartość 'value' do dyrektywy 'directive' $csp->add('directive', 'value'); // Umożliwia dodanie wielu wartości na raz do jednej dyrektywy $csp->add('directive', [ 'value1', 'value2' ]);
Zalecam na początku zablokować wszystkie zasoby, żeby nie utworzyć sobie „martwego pola”. Dzięki takiemu zabiegowi stworzymy białą listę, i zezwolimy przeglądarce ładować tylko to co potrzebujemy. Zasady jakie powinniśmy przyjąć na początku:
default-src 'none'
script-src 'self'
– pliki JS,connect-src 'self'
– AJAX + WebSocket + EventSource,img-src 'self'
– obrazki,media-src 'self'
– audio + video,style-src 'self'
– pliki CSS,font-src 'self'
– pliki fontów,frame-src 'self'
oraz child-src 'self'
– ramki.Dzięki tak zbudowanych zasadach, blokujemy wszystko, oprócz listy zasobów z drugiego punktu. Blokowane są nawet skrypty JS inline oraz style CSS inline.
Uwaga!
Należy pamiętać o specjalnym użyciu wartości ‚none’, ‚self’ oraz ‚unsafe-inline’ – wartości te muszą być napisane w apostrofach. Dla przeglądarki oznacza to wartość specjalną – w innym przypadku przeglądarka potraktuje ją jako domenę lub nonce (więcej o tym czym jest nonce w dalszej części artykułu)
Przykład podstawowej konfiguracji w pseudokodzie:
$csp // Blokujemy wszystko ->add('default-src', "'none'") // Zezwalamy na ładowanie z własnej domeny skryptów JS... ->add('script-src', "'self'") // ...AJAX + WebSocket + EventSource... ->add('connect-src', "'self'") // ...obrazki... ->add('img-src', "'self'") // ...audio + video... ->add('media-src', "'self'") // ...style CSS... ->add('style-src', "'self'") // ...fonty... ->add('font-src', "'self'") // ...ramki frame i iframe. ->add('frame-src', "'self'") ->add('child-src', "'self'");
Content-Security-Policy: default-src 'none'; img-src 'self'; style-src 'self' https://fonts.gstatic.com;
Każdy kolejny typ z treści (dyrektywę) z poszczególnymi regułami (wartościami) oddzielamy średnikiem. Jeśli mamy więcej reguł jednego typu – oddzielamy je zwykłą spacją. Reguły specjalne, takie jak ‚nonce’ czy ‚self’ piszemy w apostrofach.
Polub stronę PHPCenter.pl by zyskać dostęp do najnowszych wiadomości ze świata PHP!
Gdy podstawowe reguły mamy już stworzone, możemy przystąpić do dodawania reguł ładowania zasobów indywidualnie do naszych potrzeb. Jak wspomniałem na początku, reguła zawiera protokół, domenę i opcjonalnie port serwera. I tak dla przykładu, jeśli chcemy użyć fontów z Google Fonts, musimy dodać następujące reguły:
$csp // Zezwalamy na ładowanie plików CSS z domeny Google ->add('style-src', 'https://fonts.googleapis.com') // Zezwalamy na ładowanie plików fontów z domeny Google ->add('font-src', 'https://fonts.gstatic.com');
Podobnie będzie wyglądać zasada dotycząca ładowania filmów z YouTube czy Vimeo:
$csp->add('frame-src', [ 'https://www.youtube.com', 'https://www.vimeo.com' ]);
W ten sposób dodajemy reguły tylko do tych podstron, na których posiadamy dany zasób. Dla przykładu, fonty potrzebujemy na wszystkich podstronach, więc te reguły dodamy w jakimś Middleware, jednak filmy YouTube posiadamy na przykład tylko w zakładce filmy, więc tą regułę dodajmy tylko w kontrolerze odpowiadającym za tą zakładkę. Można oczywiście dodać wszystkie reguły do każdej z podstron – nie jest to błędem – jednak zaleca się definiowanie reguł tylko w przypadkach gdy faktycznie będą one używane na danej podstronie.
Jak już wspomniałem, domyślnie zablokowane są style CSS i skrypty JS inline. Jednak jeśli chciałbyś by style CSS inline nie były blokowane, należy dodać poniższą regułę do style-src, by odblokować style inline.
$csp->add('style-src', "'unsafe-inline'");
Unikaj tego rozwiązania dla skryptów JS inline!
To samo możemy użyć na dyrektywie ‚script-src’ jednak zdecydowanie odradzam to rozwiązanie ze względu na ogromne możliwości ataków XSS.
Kto nie pisze skryptów JavaScript inline, w kodzie HTML, ręka do góry? 🙂 Nie da się tego uniknąć w 100%. Problemem natomiast jest możliwość wstrzyknięcia takiego kodu inline przez atakującego, na przykład w treści komentarza. CSP daje możliwość całkowitego zablokowania takich skryptów inline na całej stronie, lub (co jest o wiele lepszym wyjściem) zdefiniowania które z nich mają zostać wykonane. Taka blokada pozwoli wykonywać na stronie tylko konkretne skrypty inline.
Skrypty JS inline są zablokowane, więc nawet jeśli zostaną one wysłane z kodem HTML to przeglądarka ich nie wykona. Możemy jednak poinformować przeglądarkę, że konkretny skrypt inline może zostać wykonany. Co robimy?
nonce=""
znacznika <script/>
podajemy nonce wygenerowany w punkcie pierwszym.Gdy przeglądarka chce wykonać skrypt inline, sprawdza czy wartość z atrybutu nonce istnieje w nagłówku odpowiedzi. Gdy istnieje – wykonuje skrypt. Jeśli nie, to pokaże w konsoli przeglądarki powiadomienie o fakcie zablokowania wykonywania skryptu JS inline.
Przykładowy kod, który generuje nonce wygląda następująco:
$string = true; $bytes = openssl_random_pseudo_bytes(16, $string); $nonce = base64_encode($bytes); $csp->add('script-src', "'nonce-{$nonce}'"); return $nonce;
Generujemy losowy ciąg bitów i kodujemy w Base64. Następnie dodajemy wygenerowany nonce do nagłówka odpowiedzi z przedrostkiem 'nonce-'
. Zauważ, że cała wartość nonce jest w apostrofie! Wartość zmiennej $nonce
wyświetlamy w atrybucie nonce=""
.
Sam tag skryptu inline wygląda podobnie do (bez przedrostka 'nonce-'
, jak to ma miejsce w regule CSP):
<script nonce="UJFBuyntFNUytfnuy"> // Kod JavaScript </script>
Nagłówek odpowiedzi zawierający tak ustawiony nonce wyglądać będzie podobnie do:
Content-Security-Policy: script-src 'nonce-UJFBuyntFNUytfnuy';
Dzięki Content Security Policy mamy możliwość kontrolowania tego, jakie zasoby ładuje przeglądarka na naszej stronie. Możemy blokować, zezwalać i ograniczać zasoby w zależności od potrzeb. Dobrze skonfigurowane CSP pozwala wyeliminować praktycznie 99% ataków XSS.
A czy Ty używasz CSP? Poleciłbyś użycie lub sam użyłbyś CSP w swojej aplikacji? Podziel się swoją opinią w komentarzu.
Polub naszą stronę aby dostawać powiadomienia o nowych, niesamowitych treściach!