XSS Protection Setup for Websites
XSS — class of attacks where attacker injects malicious JavaScript into pages seen by other users. Consequences: session and cookie theft, form input interception, page defacement, redirect to phishing sites.
Three XSS Vectors
Reflected XSS — malicious payload passed via URL parameters and immediately displayed. Example: https://example.com/search?q=<script>alert(document.cookie)</script>.
Stored XSS — payload saved in database (comments, user profile) and executed for everyone viewing the page.
DOM-based XSS — payload processed by client-side JavaScript without server involvement. Dangerous because server filters don't see it.
Output Escaping
Main defense tool — contextual escaping when outputting data.
PHP/Blade (Laravel):
{{ $userInput }} {{-- automatically escapes: & < > " ' --}}
{!! $trustedHtml !!} {{-- RAW output — only for trusted content --}}
React escapes JSX values by default:
// Safe
<div>{userInput}</div>
// Dangerous — use only with sanitized content
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
Vue:
<!-- Safe -->
<span>{{ userInput }}</span>
<!-- Dangerous -->
<span v-html="userInput"></span>
HTML Content Sanitization
If users can input formatted text (WYSIWYG editors), need sanitization library:
// DOMPurify — standard for browser sanitization
import DOMPurify from 'dompurify';
const cleanHtml = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li'],
ALLOWED_ATTR: ['href', 'target'],
ALLOW_DATA_ATTR: false,
});
Server-side (PHP):
// HTMLPurifier
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'b,i,em,strong,a[href|title],p,ul,li');
$purifier = new HTMLPurifier($config);
$clean = $purifier->purify($userInput);
Security Headers
Content-Security-Policy — main XSS defense at browser level (see separate CSP section).
X-XSS-Protection — deprecated header, modern browsers ignore it. CSP replaces it completely.
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
Secure Cookie Flags
Even with successful XSS, attacker won't get session cookie if protected:
// PHP
setcookie('session', $value, [
'httponly' => true, // inaccessible via document.cookie
'secure' => true, // HTTPS only
'samesite' => 'Strict' // not sent in cross-site requests
]);
# Nginx — add flags to Set-Cookie
proxy_cookie_path / "/; HttpOnly; Secure; SameSite=Strict";
Input Validation
Don't rely on output escaping alone — validate data on input:
// Laravel Validator
$validated = $request->validate([
'name' => 'required|string|max:255|regex:/^[a-zA-Zа-яёА-ЯЁ\s\-]+$/u',
'website' => 'nullable|url',
'comment' => 'required|string|max:5000',
]);
DOM-based XSS: Dangerous Patterns
// Dangerous
document.getElementById('output').innerHTML = location.hash.slice(1);
eval(userData);
setTimeout(userCallback, 1000); // if userCallback is string
// Safe
document.getElementById('output').textContent = location.hash.slice(1);
Special attention to: innerHTML, outerHTML, document.write, eval, Function(), setTimeout/setInterval with string arguments.
Testing
Tools for checking:
- OWASP ZAP — automatic scanner
- Burp Suite Community — manual testing
- DOM XSS Scanner — browser extension
- Browser DevTools: Security tab, CSP header verification
Implementation Timeline
- Audit existing code for XSS vulnerabilities: 1–3 days
- Fix found issues: 2–5 days (depends on scope)
- CSP setup + tooling: 3–5 days







