Cross-Site Scripting (XSS) Explained
Cross-Site Scripting (XSS) is one of the most common and dangerous vulnerabilities affecting modern web applications. It has persisted for decades despite advancements in frameworks, libraries, and security awareness. The reason for its longevity is simple: many applications rely heavily on user-generated input, and when that input isn’t properly validated or encoded, attackers can manipulate it to execute code directly in a victim’s browser. In this article, I hope to give a good understanding of how to protect your application.
The real danger of XSS lies in its versatility. It doesn’t exploit a flaw in browsers themselves but instead abuses how applications deliver dynamic content. Whether through comments, URLs, or client-side JavaScript, XSS attacks can impact confidentiality, integrity, and availability of data. For organizations, this often translates into stolen user sessions, defaced sites, leaked sensitive data, or even full compromise of business-critical systems.
Because of its widespread impact and the relatively low barrier for attackers to exploit it, XSS consistently ranks high on vulnerability reports such as the OWASP Top 10.
What are XSS Attacks?
XSS is a type of security vulnerability that allows attackers to inject malicious code into otherwise trusted web applications. These injections can take the form of JavaScript, CSS, or even HTML, and are executed in the victim’s browser, effectively bypassing the same-origin policy that normally isolates websites from each other. Beyond traditional web browsers, XSS can also affect mobile applications built on web technologies, making the impact even broader.
Even if the victim is a regular user, an attacker can steal sessions, credentials, or sensitive data accessible to that account. When the victim has elevated privileges, the impact becomes even greater, potentially giving the attacker broad control over application functionality and critical information.
How Does XSS Work?
XSS exploits occur when untrusted user input is incorrectly handled by a web application and subsequently included in its output without proper sanitization or encoding. This allows attackers to run arbitrary code in the victim’s browser. Typical attack vectors include form inputs, query parameters, HTTP headers, and Document Object Model (DOM) manipulation. Once injected, malicious scripts can impersonate users, steal credentials, perform unauthorized actions, and modify website content.
Types of XSS Attacks
XSS vulnerabilities are commonly categorized into following types:
Reflected XSS
The malicious input is immediately reflected in the application’s response, often via query parameters. Unlike stored XSS, reflected XSS does not persist on the server, which means each attack must be delivered to the victim individually, usually through a crafted link. Because of this, attackers frequently rely on phishing campaigns or social engineering to entice victims into clicking these malicious links.
Although often considered less severe than stored XSS, reflected XSS can still lead to significant damage. Attackers may steal session cookies, capture credentials, or redirect users to malicious websites. Combined with other vulnerabilities, such as web cache poisoning, reflected XSS can become more persistent and delivered to multiple users.
Stored XSS (Also Known as Persistent XSS)
The malicious payload is stored on the server (e.g., in a database, user profile, or comment section) and served to multiple users when they access the affected page. This makes stored XSS particularly dangerous because it can automatically impact every user who views the compromised content, rather than relying on the attacker to trick individual victims with a crafted link.
Stored XSS is often found in features that rely heavily on user-generated content, such as forums, blogs, social media platforms, or product reviews. Once an attacker has successfully injected malicious code into these areas, the payload is delivered to all subsequent visitors, creating the potential for widespread compromise. Unlike reflected XSS, which is usually one-time and ephemeral, stored XSS has a persistence that magnifies its impact.
DOM-Based XSS
The vulnerability originates in client-side JavaScript code rather than server-side rendering and is most often seen in single page applications. Malicious data is processed by the DOM environment, usually extracted from sources such as window.location, document.referrer, or fragments in the URL.
Attackers’ Objectives
The motivation behind XSS attacks often includes:
- Session hijacking: Stealing cookies to impersonate users.
- Credential theft: Capturing login details through fake forms.
- Data exfiltration: Reading sensitive information accessible to the victim.
- Defacement: Altering the appearance of websites.
- Privilege escalation: Leveraging user roles to gain deeper access.
- Malware distribution: Injecting trojans or redirecting users to malicious websites.
XSS Protection Strategies
Protecting applications from XSS requires layered defenses:
Output Encoding: Escape special characters before rendering user input in HTML, attributes, or JavaScript contexts.
The following PHP code is vulnerable to reflected XSS.
| <div>Welcome, <?= $_GET['name'] ?></div> |
While the following PHP 8.1 and up encodes HTML characters so they are printed as text instead of HTML.
| <div>Welcome, <?= htmlspecialchars($_GET['name']) ?></div> |
Input Validation: Enforce strict input validation (e.g., numeric fields only accept numbers, URLs require specific protocols).
The following JavaScript code takes a username and validates that it is only alphanumeric. Effectively blocks dangerous input.
|
if (!/^[a-zA-Z0-9]+$/.test(req.body.username)) { return res.status(400).send('Invalid input'); |
In input validation it is important to match against a defined rule set. Age should only be a number. A selection-input should be matched against the selection.
Content Security Policy (CSP): Limit where scripts can be loaded from and reduce the impact of XSS exploits.
Ther web server has specified that scripts are only allowed to be loaded from the specified sources and blocks inline scripts from executing except those allowed with a special encoded nonce.
| Content-Security-Policy[EW1] [FK2] : default-src 'self'; script-src 'self' https://trusted.cdn.com 'nonce-d2luZ2NoaWxkY2hpY2tlbg==' |
Framework Security Features: Use modern frameworks and libraries that handle encoding automatically.
In the framework React, the input is printed directly, therefore no special encoding is needed due to React automatically applying it. Validation still needs to be performed when accepting the data from the user. In this example we used React but all of the largest front end frameworks automatically applies encoding.
| <p>{userInput}</p> |
Developers need to actively specify that they want to set HTML input for the vulnerability to arise.
| <p dangerouslySetInnerHTML= /> |
Security Testing: Regular penetration testing and code reviews to identify vulnerabilities early in the development process.
Multiple of these layered defenses should be implemented. If only one is implemented, a hacker often can circumvent them but when they are used together it creates a good security posture.
How Knowit Can Help
At Knowit, our dedicated vulnerability researchers bring many years of experience in testing web applications and complex systems. We combine deep technical expertise with a pragmatic approach to help organizations strengthen their security posture and build trust in their digital services.
Our services include:
- In-depth penetration testing tailored to your applications and infrastructure.
- Code reviews focused on identifying security flaws early in the development lifecycle.
- Training and workshops to equip your development and operations teams with secure coding practices.
- Guidance on implementing:
- Continuous monitoring
- Incident response
- Long-term security strategies.
- For more information, please visit our page (in Swedish) on Säkerhetsgranskningar inklusive penetrationstester.