← News
/PLAYBOOK · 2026-07-27

XSS Filter and Defense: Practical Configuration Guide

Practical XSS defense covering output encoding, CSP, input filtering, ModSecurity WAF rules, and HttpOnly cookies. Includes deployable config.

Scope

XSS (Cross-Site Scripting) is among the most common web vulnerabilities. It applies to every public-facing web site. This tutorial is for ops, security engineers, and developers implementing XSS defenses on self-hosted or maintained apps. The config and encoding rules apply to PHP, Python, Java, and Node.js.

Prerequisites

How it works

XSS injects malicious script into a page. Other users execute it in their browsers. Three types: reflected XSS (script arrives in a URL param and is echoed immediately), stored XSS (script is persisted in the database then rendered), DOM XSS (client-side DOM mutation; fully client-side).

Core rule: never trust user input. Any user-controlled data (URL params, form posts, HTTP headers, cookies, uploaded filenames) must be filtered, escaped, or encoded before it is written to HTML.

Steps

Step 1: Output encoding (baseline defense)

Encode user data on output. Match encoding to context:

`// PHP: htmlspecialchars function
echo htmlspecialchars($user_input, ENT_QUOTES | ENT_HTML5, 'UTF-8');

// Python Flask: Jinja2 template auto-escaping
{{ user_input }}  <!-- Jinja2 enables HTML escaping by default -->

// Java: use OWASP Java Encoder
import org.owasp.encoder.Encode;
out.println(Encode.forHtml(user_input));

// Node.js: use the escape-html package
const escape = require('escape-html');
res.send(escape(user_input));`

Step 2: Set Content Security Policy (CSP)

CSP is a browser-enforced control. HTTP response headers restrict resource origins. Nginx config:

`# Nginx config: CSP header
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-{random-value}' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self';" always;

# Apache config
Header set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';"`

Key CSP directives: script-src 'self' allows same-origin scripts only; object-src 'none' blocks plugins such as Flash; frame-ancestors 'self' blocks clickjacking.

Step 3: Configure X-XSS-Protection header

`# Nginx
add_header X-XSS-Protection "1; mode=block" always;

# Apache
Header set X-XSS-Protection "1; mode=block"`

Step 4: Input filtering (server-side allowlist)

Validate user fields strictly. Prefer allowlists over denylists:

`// PHP: allow only letters, digits, and specific characters
$clean_input = preg_replace('/[^a-zA-Z0-9_@.\\-]/', '', $user_input);

// Python: use the bleach library to sanitize HTML
import bleach
allowed_tags = ['b', 'i', 'u', 'a', 'p', 'br']
clean_html = bleach.clean(user_html, tags=allowed_tags, strip=True)

// Java: use OWASP Java HTML Sanitizer
import org.owasp.html.Sanitizers;
String clean = Sanitizers.FORMATTING.sanitize(user_html);`

Step 5: ModSecurity WAF rules

ModSecurity is an open-source WAF. It intercepts XSS in front of Nginx/Apache:

`# OWASP CRS Core Rule Set: XSS detection
SecRule ARGS|ARGS_NAMES|REQUEST_HEADERS|REQUEST_HEADERS_NAMES|REQUEST_BODY|REQUEST_BODY_NAMES "@detectXSS" \
    "id:941100,phase:2,deny,status:403,msg:'XSS Attack Detected'"

# Custom rule: block common XSS payloads
SecRule ARGS "(?i:<script.*?>|javascript:|onerror=|onload=|onclick=)" \
    "id:950001,phase:2,deny,status:403,msg:'Common XSS Payload Blocked'"

# Rule: block scripts in data: URIs
SecRule ARGS "(?i)data:text/html" \
    "id:950002,phase:2,deny,status:403,msg:'Data URI XSS Blocked'"`

Step 6: Set HttpOnly and Secure cookies

Stop JavaScript from stealing cookies:

`// PHP: set Session Cookie
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');

// Nginx: configure application-layer Cookie header
proxy_cookie_path / "/; HTTPOnly; Secure; SameSite=Strict";`

Verify

After deploy, confirm XSS defenses work:

`# Test 1: reflected XSS test
curl 'https://your-site.com/search?q=<script>alert(1)</script>'
# Expected result: script tags are HTML-escaped and displayed as text; alert does not pop up

# Test 2: check response headers
curl -I https://your-site.com/ | grep -E 'X-XSS-Protection|Content-Security-Policy'
# Expected:
# X-XSS-Protection: 1; mode=block
# Content-Security-Policy: default-src 'self'; ...

# Test 3: CSP violation test (script should be blocked)
curl -I https://your-site.com/`

FAQ

Q1: Site features break after CSP?

Over-strict CSP can block third-party scripts (analytics, ads, chat). Start with report-only (Content-Security-Policy-Report-Only). Collect violations, tighten the allowlist, then enforce. Use nonce or hash to allow specific inline scripts.

Q2: How to prevent XSS with rich-text editors (TinyMCE/Quill)?

Rich-text editors emit HTML. htmlspecialchars would destroy formatting. Sanitize with OWASP HTML Sanitizer or bleach. Keep safe tags only (b, i, u, a, p, br, ul, ol, li). Strip event-handler attributes (onerror, onclick) and javascript: URLs.

Takeaway

XSS defense is layered. Output encoding is the floor. CSP is browser-level. Input filtering shrinks the attack surface. WAF blocks known payloads. HttpOnly cookies limit impact. Single controls get bypassed. Combine them. Add XSS checks to CI/CD. Scan regularly with tools such as OWASP ZAP and XSStrike.