September 23, 2026
Understanding Cross-Site Scripting (XSS) with Examples
Cross-Site Scripting (XSS) is a web security attack where an attacker injects malicious scripts into a trusted website. These scripts…
By Rifat Arefin
5 min read
Cross-Site Scripting (XSS) is a web security attack where an attacker injects malicious scripts into a trusted website. These scripts somehow run in another user's browser and can steal cookie, hijack sessions, or perform unwanted actions.
XSS happens when a website displays user's input without proper validation or encoding.
How XSS Works
At its core, XSS takes advantage of the trust users having on a website. Instead of attacking users directly, attackers inject malicious code into the website, which is then unknowingly delivered and executed in the victim's browser.
Here's a breakdown of the typical XSS attack flow:
- The attacker finds a website that doesn't properly sanitize user input. This input section could be a comment section, a search bar, or any other field where users can enter data.
- The attacker creates a malicious script, often JavaScript, and put it into this vulnerable input field.
- The malicious script is stored on the website's server (e.g., in a database).
- A victim visits the page containing the injected script.
- The victim's browser executes the malicious script, believing it to be a legitimate part of the website.
- The script can then perform various malicious actions, such as — stealing cookies and session tokens, redirecting the user to a phishing website, displaying fake login forms to capture credentials and so so on.
Types of XSS
There are three main types of XSS attacks:
Persistent XSS
This is the most dangerous type of XSS. The malicious script is permanently stored on the target server (e.g. in a database). When a user visits the affected page, the script is executed.
Imagine a simple forum website where users can post messages. The website stores these messages in a database and displays them to other users. The forum website doesn't properly sanitize user input when storing messages.
An attacker posts the following message:
Hello everyone! <script>alert('XSS Attack!');</script>Hello everyone! <script>alert('XSS Attack!');</script>The message, including the malicious script, is stored in the forum's database. When another user visits the website and check the new posts, his browser executes the script:
alert('XSS Attack!');alert('XSS Attack!');This would display an alert box. A more sophisticated attack could steal the user's cookies and send them to the attacker's server.
Non-Persistent XSS
In this type of attack, the harmful code is not stored on the website's server. Instead, the attacker puts the malicious code inside a link or request. When the victim opens that link, the website sends the code back in its response, and the victim's browser runs it.
Imagine a website has a search feature, user searches for laptop and the following URL creates to search laptops — https://example.com/search?q=laptop
The corresponding html page be like –
<h1>You searched for: Laptop</h1><h1>You searched for: Laptop</h1>Now an attacker creates this link: https://example.com/search?q=<script>alert("Hacked!")</script>
The attacker sends this link to someone (maybe through email) saying: "Hey, check this interesting product!". When the victim clicks it, the website displays:
You searched for: <script>alert("Hacked!")</script>You searched for: <script>alert("Hacked!")</script>The browser executes the script and shows alert: Hacked!
The important part: the script was never saved in the website database — it only came through the link and was immediately reflected back to the user.
DOM-based XSS
DOM-based XSS is a type of XSS where the vulnerability exists entirely in the browser-side JavaScript. The server may never receive or modify the malicious input.
The basic idea —
A web page takes data from a source controlled by the attacker and puts it into a dangerous sink that interprets it as HTML/JavaScript.
Suppose a page contains:
<div id="output"></div>
<script>
const name = new URLSearchParams(location.search).get("name");
document.getElementById("output").innerHTML = name;
</script><div id="output"></div>
<script>
const name = new URLSearchParams(location.search).get("name");
document.getElementById("output").innerHTML = name;
</script>A normal URL might be: https://example.com/?name=Alice
The JavaScript effectively does: output.innerHTML = "Alice";
But if an attacker supplies HTML/JavaScript as the name value, the browser may interpret it as markup rather than plain text. The important point is that the vulnerable code is running in the browser.
Sources and sinks
A useful way to understand DOM XSS is to learn the concept of sources and sinks.
Sources: Places where JavaScript can obtain attacker-controlled data:
- location.search — gives us the query string. For example,
[https://example.com/products?id=123](https://example.com/products?id=123)``.Here?id=123is the value of location.search. - location.hash — gives us the fragment identifier, which starts with #. For example,
https://example.com/products?id=123#reviews. Here#reviewsis the value of location.hash - location.href — gives us the entire current URL. For example,
https://example.com/products?id=123&category=phone#reviewshere, location.href contains the entire url – https://example.com/products?id=123&category=phone#reviews
Dangerous sinks: Operations that can interpret that data as HTML or executable code:
- element.innerHTML = data;
- element.outerHTML = data;
- document.write(data);
- eval(data);
How to Prevent XSS
Preventing XSS requires a multi-layered approach:
1. Input Validation
- Validate all user input on the server-side.
- This includes checking the data type, length, format, and allowed characters.
- Reject any input that doesn't conform to the expected format.
2. Output Encoding
Encode all user input before displaying it on the page. This converts potentially dangerous characters into their safe equivalents. The specific encoding method depends on the context:
2.1 HTML Encoding
Instead of inserting the raw input, we first encode special characters. For example, The user's malicious input: <img src=x onerror="alert('XSS')">
It becomes <script>alert("Hacked!")</script>
Now our HTML becomes:
<p>Hello, <script>alert("Hacked!")</script> </p><p>Hello, <script>alert("Hacked!")</script> </p>Example code
<!DOCTYPE html>
<html>
<head>
<title>XSS Encoding Demo</title>
</head>
<body>
<input type="text" id="username" placeholder="Enter username">
<button onclick="showUsername()">Submit</button>
<div id="output"></div>
<script>
function htmlEncode(value) {
return value.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function showUsername() {
const username = document.getElementById("username").value;
const encodedUsername = htmlEncode(username);
document.getElementById("output").innerHTML = encodedUsername;
}
</script>
</body>
</html><!DOCTYPE html>
<html>
<head>
<title>XSS Encoding Demo</title>
</head>
<body>
<input type="text" id="username" placeholder="Enter username">
<button onclick="showUsername()">Submit</button>
<div id="output"></div>
<script>
function htmlEncode(value) {
return value.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function showUsername() {
const username = document.getElementById("username").value;
const encodedUsername = htmlEncode(username);
document.getElementById("output").innerHTML = encodedUsername;
}
</script>
</body>
</html>2.2 JavaScript Encoding
If user input is embedded into a <script> block, HTML-encoding alone isn't enough, because we're now inside a JS string literal, not HTML markup.
Here the dangerous characters are different: ', ", , newlines, </script>.
Vulnerable code:
<script>
var username = "<?php echo $_GET['user']; ?>";
</script><script>
var username = "<?php echo $_GET['user']; ?>";
</script>Attack input: ";alert(document.cookie);//
Result without encoding:
<script>
var username = "";alert(document.cookie);//";
</script><script>
var username = "";alert(document.cookie);//";
</script>The attacker closed the string early with ", injected a new JS statement (alert(...)), and commented out the rest with //.
With JS encoding, special characters are escaped using JS escape sequences so they stay part of the string literal rather than breaking out of it:
Result with encoding:
<script>
var username = "\x22;alert(document.cookie);\x2F\x2F";
</script><script>
var username = "\x22;alert(document.cookie);\x2F\x2F";
</script>3. URL Encoding
Suppose a site builds a URL like this:
const username = document.getElementById("username").value;
window.location.href = "/profile?username=" + username;const username = document.getElementById("username").value;
window.location.href = "/profile?username=" + username;The developer intends username to be one parameter value. Now suppose the attacker enters: Alice&admin=true. The resulting URL becomes: /profile?username=Alice&admin=true
The server doesn't see one username anymore. It sees two parameters:
- username = Alice
- admin = true
The & has special meaning in a URL: it separates parameters. URL encoding fixes that. Use:
const username = document.getElementById("username").value;
const safeUsername = encodeURIComponent(username);
window.location.href = "/profile?username=" + safeUsername;const username = document.getElementById("username").value;
const safeUsername = encodeURIComponent(username);
window.location.href = "/profile?username=" + safeUsername;Now Alice&admin=true becomes approximately: Alice%26admin%3Dtrue and the URL becomes: /profile?username=Alice%26admin%3Dtrue
The server interprets that as username = "Alice&admin=true" rather than username = "Alice" and admin = "true"
Content Security Policy (CSP)
CSP is a security standard that allows you to control the resources that the browser is allowed to load for a specific page. This can help prevent XSS attacks by restricting the sources from which scripts can be executed.
Use a Framework
Modern web development frameworks often provide built-in XSS protection mechanisms. For example, React, Angular, and Vue.js automatically escape data by default.
Regularly Update Software
Keep your web server, framework, and other software components up to date with the latest security patches.
Find me at —
Rifat Arefin | Backend Software Engineer Portfolio of Rifat Arefin, a backend-focused software engineer specializing in C++, Python, Django, PHP, scalable…
#XSS #CrossSiteScripting #CyberSecurity #WebSecurity #ApplicationSecurity #AppSec #EthicalHacking #WebDevelopment #InfoSec #SecurityTesting #OWASP #JavaScript #CyberSecurityAwareness #RifatArefin