Răspuns scurt
- Configurezi în nginx headerele X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy și frame-ancestors, plus server_tokens off, pe toate răspunsurile, cu add_header ... always.
- Content-Security-Policy o introduci întâi ca Content-Security-Policy-Report-Only și o treci pe blocare doar după ce rapoartele nu mai arată resurse legitime blocate.
- frame-ancestors din CSP înlocuiește X-Frame-Options, pe care îl păstrezi pentru browserele vechi.
- Testezi rezultatul cu curl -I și cu un scaner public de headere, după fiecare deploy.
- Header obligatoriu pe orice site
- X-Content-Type-Options: nosniff
- Împotriva clickjacking
- frame-ancestors 'self' în CSP (+ X-Frame-Options)
- Cum introduci CSP
- Content-Security-Policy-Report-Only, apoi blocare
- Capcana nginx
- add_header într-un location anulează cele din server
Ce face fiecare header și împotriva a ce
Content-Security-Policy e cel mai puternic: listează originile de unde browserul acceptă scripturi, stiluri, imagini și conexiuni, ceea ce reduce drastic efectul unui XSS, pentru că scriptul injectat nu are voie să ruleze. X-Content-Type-Options: nosniff oprește browserul să „ghicească” tipul unui fișier și să execute ca script ceva servit ca text. frame-ancestors, și predecesorul X-Frame-Options, împiedică încadrarea site-ului într-un iframe străin, baza clickjacking-ului. Referrer-Policy controlează cât din URL pleacă spre alte site-uri. Permissions-Policy dezactivează camera, microfonul sau geolocația.
Cum le pui în nginx fără să rupi site-ul
Regula de aur în nginx: directivele add_header dintr-un bloc location înlocuiesc complet lista moștenită din server. Dacă adaugi un add_header pentru cache într-un location pentru imagini, headerele de securitate dispar de pe acele răspunsuri. Soluția e un fișier inclus în fiecare bloc care are propriile add_header. Adaugi always ca headerele să apară și pe răspunsurile 4xx și 5xx. CSP o scrii în report-only și o urmărești câteva săptămâni: scripturile inline, fonturile externe, widgeturile de chat apar toate în rapoarte. Abia apoi o treci pe blocare, cu nonce pentru scripturile inline.
- server_tokens off ascunde versiunea nginx din antet și din paginile de eroare
- HSTS are pagina lui în acest ghid; se adaugă doar pe răspunsurile HTTPS
- object-src 'none' și base-uri 'self' închid două vectori des uitați
Headerele de securitate HTTP recomandate pentru un site de producție| Header | Valoare recomandată | Ce previne |
|---|
| Content-Security-Policy | default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self' | XSS, injectare de resurse, clickjacking |
|---|
| X-Content-Type-Options | nosniff | Executarea fișierelor cu tip greșit |
|---|
| X-Frame-Options | SAMEORIGIN (pentru browsere vechi) | Clickjacking |
|---|
| Referrer-Policy | strict-origin-when-cross-origin | Scurgerea URL-urilor cu token-uri |
|---|
| Permissions-Policy | camera=(), microphone=(), geolocation=() | Acces la senzori din pagini compromise |
|---|
| Strict-Transport-Security | max-age=31536000; includeSubDomains (doar pe HTTPS) | Downgrade la HTTP |
|---|
| Server | server_tokens off | Identificarea versiunii pentru exploatare |
|---|
Comenzi și configurări
/etc/nginx/snippets/security-headers.conf, inclus în fiecare bloc cu add_headeradd_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; report-uri /csp-report" always;
Blocul server care le foloseșteserver_tokens off;
server {
listen 443 ssl;
http2 on;
server_name exemplu.ro;
include snippets/security-headers.conf;
location ~* \.(png|jpg|webp|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable";
include snippets/security-headers.conf; # altfel headerele dispar aici
}
location = /csp-report {
access_log /var/log/nginx/csp.log;
return 204;
}
}
Verifici după deploycurl -sI https://exemplu.ro/ | grep -iE "content-security|x-frame|x-content-type|referrer|permissions|strict-transport|^server"
curl -sI https://exemplu.ro/pagina-inexistenta | grep -i x-content-type
Exemple practice
Magazin online din Cluj cu XSS într-un câmp de recenzii
Un script injectat într-o recenzie citea cookie-urile utilizatorilor autentificați. Cu CSP în modul de blocare și script-src limitat la propriul domeniu, scriptul nu s-a mai executat, iar rapoartele CSP au indicat exact pagina afectată înainte ca cineva să reclame.
Platformă de programări din Timișoara încadrată într-un iframe
Un site terț încadra pagina de login într-un iframe invizibil, suprapus peste un buton de „descarcă”. frame-ancestors 'self' a oprit încadrarea. Verificarea a arătat că headerul lipsea pe /login din cauza unui add_header pentru cache din acel location.
Greșeli frecvente
- Pui headerele în blocul server și le pierzi pe rutele care au propriul add_header, fără să observi.
- Publici CSP direct în modul de blocare și rupi widgetul de plată sau chat-ul de suport.
- Folosești 'unsafe-inline' în script-src „temporar” și CSP nu mai oprește niciun XSS.
- Omiți always și headerele lipsesc de pe paginile de eroare.
Cum te ajută 4hosting
- Serviciul Hardening & Compliance configurează headerele și trece CSP din report-only în blocare pe baza rapoartelor, în orele incluse: 1–8 lunar la VPS, 10 la dedicate și GPU, cerute din panoul de client, secțiunea „Servicii DevOps”; orele se consumă în luna calendaristică și nu se reportează.
- Serviciul React / Next.js adaptează aplicația la o CSP strictă, cu nonce pe scripturile inline, fără 'unsafe-inline'.
- Serviciul Pentesting & Security Audit include verificarea headerelor și a politicii CSP în raportul de audit web.
Întrebări conexe
CSP rupe Google Analytics, Stripe sau chat-ul de suport?
Doar dacă nu le declari. Fiecare serviciu terț publică originile de care are nevoie în script-src, connect-src și frame-src. Perioada de report-only e exact pentru asta: rapoartele îți arată ce origini lipsesc din politică. Le adaugi explicit, nu deschizi politica cu wildcard.
Mai am nevoie de X-Frame-Options dacă am frame-ancestors?
frame-ancestors are prioritate în toate browserele moderne și e mai flexibil, permițând mai multe origini. X-Frame-Options îl păstrezi pentru compatibilitate cu browsere vechi; când amândouă există, browserul modern ignoră X-Frame-Options. Nu strică nimic să le ai pe ambele.
Aceste headere se pun în nginx sau în aplicație?
Cele statice, nosniff, Referrer-Policy, Permissions-Policy, frame-ancestors, în nginx, ca să acopere și fișierele statice și paginile de eroare. CSP cu nonce per cerere trebuie generată de aplicație, pentru că nonce-ul se schimbă la fiecare răspuns. Le verifici pe ambele straturi ca să nu le dublezi.