XSS támadások kivédése: Mikor nem elég csak az escaping?
Sziasztok! Ma egy olyan témáról szeretnék írni, amiről mindenki hallott, de amit még mindig rengeteg projektben elnézünk vagy félreértünk: az XSS támadásokról és helyes kivédésükről. Webfejlesztőként talán a legrémesebb érzés, amikor megtudod, hogy az alkalmazásodban keresztül szkripteket futtathatnak a felhasználók – és nem azért, mert te írtad őket.
Mi is az az XSS valójában?
Cross-Site Scripting – röviden XSS – azt jelenti, hogy támadó képes szkriptkódot „beültetni” a te weboldaladra, ami aztán más felhasználók böngészőjében fut le. Képzeld el: egy megjegyzés mezőbe nem egy kedves üzenetet írnak, hanem JavaScript kódot, ami ellopja a bejelentkezett felhasználók session cookie-ját. Vagy átirányítja őket egy phishing oldalra. Vagy épp crypto mining scriptet futtat a látogatók gépein.
Az igazi probléma? Sokszor mi, fejlesztők vagyunk a hibásak, mert nem kezeljük megfelelően a felhasználói adatokat.
A két fő fegyver: Escaping és Sanitization
Itt jön a két kulcsfogalom, amiket sokan összekevernek, pedig különböző dolgokra valók.
Escaping azt jelenti, hogy veszed a speciális karaktereket, és átalakítod őket „ártalmatlan” formára. Például a < karaktert <-re cseréled, így a böngésző nem HTML tagként, hanem sima szövegként jeleníti meg.
Sanitization viszont azt jelenti, hogy „kitisztítod” az adatot – eltávolítod a veszélyes részeket, de megtartod a biztonságos tartalmat. Például megengeded a strong taget, de kitörlöd a script taget.
PHP backend: Mindig escape-elj, de hol?
A leggyakoribb hiba, hogy csak egy helyen escape-elünk. De figyelj:
<?php
// ROSSZ - csak HTML escape, de mi van ha JavaScriptbe kerül?
echo htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');
// JÓ - context-aware escaping
class SecurityHelper {
public static function escapeForHTML($input) {
return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
public static function escapeForJS($input) {
return json_encode($input, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
}
public static function escapeForAttribute($input) {
return htmlspecialchars($input, ENT_QUOTES, 'UTF-8', false);
}
}
// Használat:
$comment = $_POST['comment'];
echo '<div class="comment" data-content="' . SecurityHelper::escapeForAttribute($comment) . '">';
echo SecurityHelper::escapeForHTML($comment);
echo '</div>';
// JavaScript részbe
echo 'var userComment = ' . SecurityHelper::escapeForJS($comment) . ';';
?>A lényeg: minden kontextusban máshogy kell escape-elni. HTML-be, HTML attribútumba, JavaScriptbe, URL-be – mind más szabályok érvényesek.
Frontend veszélyek: Amikor a JavaScript is sebezhető
Sokan azt hiszik, hogy ha a backend escape-elt, akkor frontenden már nem kell aggódni. Hát, nem egészen.
// VESZÉLYES jQuery példa
$(document).ready(function() {
// Rossz - közvetlen HTML befecskendezés
$('#commentContainer').html(userSuppliedData);
// Még rosszabb - eval a user adatával
var userScript = getUserInput();
eval(userScript);
// JÓ megoldások
// 1. Szövegként kezeljük
$('#commentContainer').text(userSuppliedData);
// 2. Ha tényleg HTML kell, sanitize-eljük
var cleanHTML = sanitizeHTML(userSuppliedData);
$('#commentContainer').html(cleanHTML);
});
// Egyszerű sanitizáció (alapvető példa)
function sanitizeHTML(input) {
var temp = document.createElement('div');
temp.textContent = input;
return temp.innerHTML;
}Bootstrap és CSS/SCSS: Az elfeledett frontend réteg
Igen, még a CSS-ben is lehet XSS probléma! Főleg, ha felhasználói CSS-t engedsz.
// Potenciális probléma - user-controlled CSS
.user-profile {
background-image: url(#{$userProvidedURL});
}
// Javítás: validáció a SCSS előtt
@function validate-url($url) {
$allowed-protocols: 'http', 'https', 'data';
$parsed: str-split($url, ':');
@if index($allowed-protocols, nth($parsed, 1)) {
@return $url;
} @else {
@return null;
}
}
.user-profile {
background-image: url(#{validate-url($userProvidedURL)});
}A legnagyobb buktatók amiket láttam
1. „De én csak internal appot fejlesztek” – a belső alkalmazásokra is támadhatnak, és ha sikerül, még nagyobb a kár 2. „Használok egy könyvtárat, az biztos jó” – régi jQuery, Bootstrap verziók, nem frissített React komponensek még mindig sebezhetők lehetnek 3. „Csak a form mezőket ellenőrzöm” – az URL paramétereket, localStorage tartalmat, API válaszokat is kezelni kell 4. „A WAF megoldja” – a Web Application Firewall csak segít, de nem helyettesíti a helyes kódolást
React speciális eset
React alapból jó munkát végez, de vannak csapdák:
// VIGYÁZAT ezzel!
function DangerousComponent({ userHTML }) {
// Ez veszélyes, ha a userHTML nem megbízható forrásból jön
return <div />;
}
// Javítás: sanitize-elés még Reactban is
import DOMPurify from 'dompurify';
function SafeComponent({ userHTML }) {
const cleanHTML = DOMPurify.sanitize(userHTML);
return <div />;
}Mit tegyél ma?
1. Context-aware escaping – mindig gondolj arra, hogy hová kerül az adat 2. Content Security Policy (CSP) header – ez megakadályozza a váratlan szkriptek betöltését 3. Input validáció – a lehető legkorábban szűrd a rossz adatot 4. Könyvtárak frissítése – a régi jQuery/CSS/JS verziók gyakran tartalmaznak ismert biztonsági réseket 5. Code review – nézzétek át egymás kódját kifejezetten biztonsági szempontból
Összegzés
Az XSS védelem nem egy egyszeri beállítás, hanem folyamatos tudatosság. Nem elég azt mondani, hogy „én escape-elek”. Tudni kell, hogy hol, mikor és hogyan. A legtöbb esetben a probléma nem az adatban van, hanem abban, hogy mi hol és hogyan jelenítjük meg.
A jó hír: a modern keretrendszerek (React, Vue, stb.) sokat segítenek, de a PHP/jQuery/Bootstrap stackkel készült projektekben is teljesen kivédhetőek a támadások – csak tudatosan kell hozzáállni.
Ne várj egy biztonsági auditra vagy egy sikeres támadásra. Kezdd el ma megnézni, hogy a te kódodban hol kerülhetnek felhasználói adatok a DOM-ba, és minden egyes helyen kérdezd meg: „Itt biztosan biztonságosan kezelem?”
A biztonság sosem kényelmes, de mindig megéri.