Understanding XSS Attacks

Cross-Site Scripting (XSS) is an attack wherein attackers inject malicious scripts into webpages viewed by other users. These scripts can bypass the same-origin policy, allowing the attacker to steal sensitive data, hijack user sessions, deface websites, or spread malware.

Imagine you're on a blog reading the comment section. Little did you know that this comment contained a hidden script that, when executed, stole your session cookie. Oops, you're now a victim of an XSS attack. But how does this happen?

How XSS Attacks Occur

XSS attacks happen in several stages:

1. The attacker identifies a target site that is vulnerable to XSS. A website is vulnerable if it blindly trusts user input and doesn't sanitize it before rendering.

Group 513650 (4).png

2. The attacker crafts a malicious script. This could be something like a malicious script or a broken image that captures and sends the user's session cookie to the attacker's server.

3. The attacker embeds this script in a vulnerable site. They could post the script as a comment, a profile status, or any other user-generated content that will be displayed to other users. In this case, the attacker embeds an erroring image that invokes its onerror attribute, which sends a request containing the user's cookie to the attacker's server.

Group 513651 (1).png

4. The script runs within the user's browser in the context of the vulnerable site. Without adequate security measures, this malicious script can potentially access any cookies, session tokens, or other sensitive information retained by the browser for that site and send them to the attacker.

Group 513652 (1).png

Types of XSS Attacks

There are three types of XSS attacks: DOM-based, reflected, and persistent.

1. DOM-based XSS

This type of XSS attack occurs when the malicious payload is executed due to modifying the DOM “on the fly” with client-side JavaScript.

DOM-based XSS is distinct in that it operates entirely client-side, without sending an HTTP request to a server. The attack unfolds within the victim's browser by manipulating the DOM. The client-side execution makes these attacks harder to detect, as traditional security measures often focus on server traffic. For attackers, this requires a nuanced understanding of JavaScript and the web application's structure to exploit successfully, making it a subtler, yet challenging attack vector.

Imagine a simple web page that reads a URL parameter and updates the content of a page using JavaScript:

<div id="content">Default content</div>
<script>
const params = new URLSearchParams(window.location.search);
document.getElementById('content').innerHTML = params.get('message');
</script>

If a user visits a link like http://example.com/index.html?message=<script>alert('Hacked!')</script>, the malicious script would run.

2. Reflected XSS

This attack occurs when a malicious script provided by an attacker is reflected off a web server, such as through a search result or error message, and executed immediately without being stored.

Reflected XSS typically requires some form of social engineering, as the victim must be tricked into clicking a specially crafted link that contains the malicious payload. Once the link is clicked, the payload is sent to the vulnerable website which then reflects the attack back to the user's browser, where the malicious script is executed. Due to its non-persistent nature, the attack happens in real time and doesn't affect other users of the site.

Imagine a simple search feature on a website that displays the search query back to the user. If the website fails to properly sanitize the query parameter before reflecting its content back on the page, the script will execute, resulting in an XSS attack.

<div>
<!-- Vulnerable Code: Reflects the user input directly without sanitization -->
You searched for: <span id="search-result"></span>
</div>
<script>
// Get the query parameter from the URL
const params = new URLSearchParams(window.location.search);
const query = params.get('query');
// Reflect the query directly into the page (vulnerable!)
document.getElementById('search-result').innerHTML = query;
</script>

If a user inputs a script instead of an ID, it'll execute on the browser, leading to a Reflected XSS attack.

3. Stored XSS

This is the most dangerous type of XSS attack. Here, the malicious script is injected directly into a website's storage, often through forms or similar input mechanisms. When other users visit this website, they unknowingly load and execute the malicious script from the site's database, making it persistent across sessions.

Consider a comment section on a blog:

<textarea id="comment"></textarea>
<button onclick="postComment()">Post Comment</button>
<div id="commentsSection"></div>
<script>
function postComment() {
const comment = document.getElementById('comment').value;
// Imagine this gets stored in a database without validation.
saveCommentToDB(comment)
document.getElementById('commentsSection').innerHTML += comment;
}
function loadComments() {
// Loads comments from db without validation
const comments = getCommentsFromDB();
document.getElementById('commentsSection').innerHTML = comments;
}
</script>

A user can post a script as a comment. When another user visits the page, that script, which is stored in the database, gets executed.


XSS attacks can have devastating effects on your users and your product.

  1. They operate covertly, hiding within legitimate websites. This is one of the main reasons XSS attacks are so effective and perilous. Users, believing they're interacting with a trusted website, may inadvertently trigger a malicious script, thinking it's part of the website's regular operations.
  2. Anyone who views a page or web application infected with an XSS payload is at risk. This makes such attacks incredibly scalable for cybercriminals. For instance, if an attacker injects a malicious script into a popular website's comment section, any user who views that comment could become a victim. This scalability underscores the importance of robust cybersecurity measures for popular websites that attract many visitors.
  3. The consequences of an XSS attack are multifaceted and can have significant implications. This includes data theft through cookies, session tokens, or other sensitive information, malware distribution by forcing a user's browser to download and run malicious software, and phishing by tricking users into providing personal data or login credentials.
  4. XSS attacks can undermine users' trust in your product.

Preventing XSS Attacks

Protection against XSS requires diligence, but there are some effective methods to mitigate them.

Reject unexpected inputs

Inputs should match expected formats; e.g. an age field should only contain numbers.

app/api/route.js
export function POST(req) {
const { age } = req.body;
if (!Number.isInteger(Number(age))) {
res.status(400).send('Invalid age input. Please enter a valid number.');
return;
}
res.status(200).send(`Your age is: ${age}`);
} else {
res.status(405).end(); // Method Not Allowed
}
};

Validate and sanitize user inputs

Use libraries such as DOMPurify to validate and sanitize user inputs, ensuring malicious scripts are cleaned before rendering.

app/api/route.js
import { sanitize } from "isomorphic-dompurify";
export function POST(req) {
const { comment } = req.body;
const sanitizedComment = sanitize(comment);
...
}

Avoid inline scripts and inline event handlers

Instead, use external scripts and bind events dynamically. This reduces the chances of accidentally executing attacker-controlled scripts.

<!-- Inline Event (Not Recommended) -->
<button onclick="alert('Button was clicked!')">Click me!</button>
----
<!-- External Event (Recommended) -->
<button id="myButton">Click me!</button>
<script>
document.getElementById("myButton").addEventListener("click", function() {
alert("Button was clicked!");
});
</script>

Set a Content Security Policy (CSP)

CSP is a browser feature that helps reduce the risk of XSS attacks. It allows websites to specify which sources are allowed, blocking any malicious or unexpected content.

<!-- This CSP allows only scripts loaded from the current site's origin -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self';">

In Next.js, you can use middleware to configure the CSP header.

(React) Avoid using dangerouslySetInnerHTML

This attribute allows for directly inserting HTML content into the React application. If the content contains malicious scripts and is not appropriately sanitized, those scripts could be executed in the user's browser. If you must use it, sanitize the content first! React’s JSX escapes all variables by default. Leverage this feature and avoid manually creating DOM content.

import React from 'react';
// Imagine this comment is fetched from a user or an external source
const userComment = '<img src=x onerror=alert("XSS Attack")>';
export function BadComment() {
// ❌ No sanitization on dangerouslySetInnerHTML
return <div dangerouslySetInnerHTML={{ __html: userComment }}></div>
}
export function BetterComment() {
const sanitizedComment = DOMPurify.sanitize(userComment);
// ⚠️ Still using dangerouslySetInnerHTML, but sanitized
return <div dangerouslySetInnerHTML={{ __html: sanitizedComment }}></div>
}
export function BestComment() {
// ✅ React automatically escapes content within curly brackets
return <div>{userComment}</div>
}

Use the HttpOnly and Secure attributes on cookies

By setting the HttpOnly flag on a cookie, you prevent it from being accessed by JavaScript. The Secure flag ensures that the cookie can only be transmitted over secure HTTPS connections. Learn more about cookies here.

Choose server-side input validation over client-side

Client-side validation is primarily for user experience. Although it offers quick feedback, it's easily manipulated. If the server blindly trusts this input, malicious actors can circumvent client-side checks, resulting in XSS vulnerabilities.

Server-side validation is the last line of defense against XSS. You can prevent the execution of undesirable scripts even if malicious input gets past client-side defenses by validating and sanitizing inputs on the server.

Before being used or displayed, all data must be validated and sanitized on the server. To reduce security gaps, always ensure validation rules and techniques are consistent on both client and server.

Be cautious with third-party libraries

Ensure any third-party React components or Next.js plugins you include do not introduce XSS vulnerabilities.


Conclusion

XSS attacks can cause significant damage, but they can be completely avoided with the appropriate preventive measures. Developers should make security a top priority and continuously update their knowledge to stay informed about emerging threats and protection mechanisms. Ensuring the security of a web application is an ongoing process, and remaining vigilant against threats like XSS is crucial.


Further Reading

Couldn't find the guide you need?