Stored XSS, explained: How to prevent stored XSS in your app

Web applications are one of the most targeted assets these days because they’re both open to the internet and have a larger attack surface. Attackers find various ways to hack web applications. And among all of those techniques, some make it to the OWASP Top Ten list of security risks. Cross-site scripting (XSS) has been one of the consistent toppers of this list, and in this post, we’ll discuss in detail one variant of cross-site scripting—stored XSS.

What is stored XSS?

XSS is a technique of injecting malicious code into a vulnerable web application. Unlike other attacks, the goal however isn’t to run it on your server, but on your users’ browsers! Depending on the attacker’s creativity and skills, this malicious code can steal sensitive information from a user, impersonate a user, or even perform some actions on behalf of the user. A couple of examples of how XSS could be dangerous are as follows:

  • Stealing session and cookie details of an admin user that could be used to get access to the admin account.
  • Having the malicious code make a financial transaction from a victim’s account to the attacker’s account.

Based on the nature of the technique (and not on the outcome), XSS can be categorized into multiple types:

  1. Reflected XSS
  2. Stored XSS
  3. DOM-based XSS

For this post, let’s stick to stored XSS.

In stored XSS, the malicious code is stored on the server of the application. Stored XSS is possible only when the application is designed to store user input. The attacker would inject the code through requests to the application. After receiving this data, the application may then store the malicious code on the server or in a database. Hence the name stored XSS.

How stored XSS works

Let’s take an example of online forums—something like Stack Overflow. Such applications let users post content on the application, and then serve it to other users. Suppose an attacker finds out that such an application is vulnerable to stored XSS in a comment field; the attacker can then inject their malicious payload into the application. After this, every time a user visits this web page or content, the application would fetch this data from the database and display it to the user. And while doing so, the application would serve the malicious code to the user.

Let’s say that an attacker wants to steal session cookies from all users visiting a page. The attacker would first craft a malicious code that would get the cookie details from a browser and send it to the attacker. Then the attacker would inject this malicious code into the vulnerable application on their target page in the form of a comment. The vulnerable application would store the comment along with the malicious code. Now, whenever any user visits the targeted page, the content of the page along with the comment from the attacker and the malicious code are sent to the user’s browser, where it becomes a standard XSS. When the browser is rendering the page, it will run the malicious code, steal cookies from the user, and send them to the attacker.

And that’s how an attacker can use stored XSS to achieve their malicious goals.

Why stored XSS matters

Out of the three types of XSS, attackers are most interested by stored XSS. The reason for that is that the reach of the malicious code through stored XSS is enormous. It takes fewer resources to target a larger number of victims. And once the malicious code is in place, its effect is continuous. If stored XSS isn’t identified and mitigated, the malicious code will keep doing its job for a ton of users and can go on for decades.

Another advantage of stored XSS is that even if infected, users have no way to know they should be careful. For example, Facebook is a well-established platform. It’s been around for a long time and has developed a factor of trust within users. You wouldn’t think a lot before opening a post on Facebook because you believe it can be trusted. But you would think twice before opening a link to/from an unknown application/email (like in the case of reflected XSS) just because you don’t know if it’s safe. If such well-established applications are vulnerable to stored XSS, it makes it even easier for the attackers to lengthen their victim list.

These reasons make stored XSS vulnerability a jackpot for attackers. Hence, it’s critical for organizations to implement measures to avoid stored XSS.

How to avoid XSS vulnerabilities in your code

Stored XSS are all about getting a malicious parameter reflected to the user. So the straightforward approach to avoid XSS vulnerabilities is to sanitize user data and handle inputs safely. Let’s look at an example and understand how sanitizing data would mitigate XSS.

Let’s say an application has an input form field where it accepts user input, stores it, and then displays it back when requested. For example, if the user input is Tony, the application would store the string Tony in its data store, and when this data is requested, it would display Tony.

If the application was written using PHP, the line to display data would look something like this:

echo "<h2>" . $name . "</h2>";

Here, $name would hold the data fetch from storage—i.e., Tony.

This line of code when rendered would look something like this:

"<h2> Tony </h2>"

Now let’s say that instead of giving Tony as input, the attacker gave <script>alert("XSS")</script> as input. The input isn’t just a regular string; it’s a malicious code. When this script is rendered on the browser when requested, it would look something like this:

echo "<h2> <script>alert("XSS")</script> </h2>";

In this case, the browser would consider the script tags as part of the response and execute the code. The reason the malicious code executes was that the input wasn’t sanitized.

You have multiple ways to prevent an XSS, as discussed here. The most efficient of them are as follows:

  • Using HTML encoding
  • Applying appropriate response headers
  • Using an Auto-Escaping Template System

How to block XSS attacks in real time

HTML encoding and other mitigations work, but it’s easy to forget them somewhere in your legacy code. A response to this is to add a dynamic protection that will scan for exploitation attempts. This way even if a hole exists, the attacker won’t be able to take advantage of it.

Web application firewalls (WAFs) have become popular over the years for web application security. You can use a WAF to detect and prevent XSS attacks in real time. WAFs can analyze traffic metrics such as sessions, packet size, and various patterns and then decide whether to block or allow the traffic. But the problem with WAFs is they’re only as good as the database of signatures. You can’t possibly have all attack signatures in your database (your app is unique after all!). By the time you add 100 patterns, there are 1,000 new ones, and the ones you have each generate some amount of noise. WAFs provide great attack detection for applications, and they’re one of the essential methods of boosting security. But WAFs alone aren’t enough. To have the best protection against XSS, you need to go beyond hardcoded logic and WAFs.

This is where Sqreen comes in. Sqreen gives you more visibility into your application. And in cybersecurity, more visibility means staying one step ahead. Using Sqreen, you can run the basic and most popular XSS checks and decide what to do with malicious detection. Sqreen provides multi-layer protection for your application, and its runtime application self-protection (RASP) examines application behavior to prevent exploits in real time.

Need a complete security package for your application? You can sign up for a free Sqreen trial here.

This post was written by Omkar Hiremath. Omkar is a cybersecurity analyst who is enthusiastic about cybersecurity, ethical hacking, data science, and Python. He’s a part time bug bounty hunter and is keenly interested in vulnerability and malware analysis.

Notify of
Inline Feedbacks
View all comments