search
HomeWeb Front-endJS TutorialYour rich text could be a cross-site scripting vulnerability

Recognize and mitigate SXSS vulnerabilities before they're exploited

By Luke Harrison

This article was originally published on IBM Developer.

Many current applications need to render rich text in HTML on their websites. In order to generate this formatted text from user input, developers use a rich text editor component. The problem? This functionality can indirectly open up both your application and data to a vulnerability known as stored cross-site scripting (SXSS).

In this article, you'll learn what an SXSS vulnerability is and review some "code smells" that you can use to check whether your applications are affected. You'll also see an example of a vulnerable application and learn a remediation strategy for this vulnerability.

What is stored cross-site scripting?

Stored cross-site scripting is a type of vulnerability that attackers can exploit to inject malicious code into a database. That code then runs on the victim's browser after being fetched and rendered by a front-end framework.

This vulnerability is extremely dangerous because it can enable attackers to steal cookies, trigger redirects, or run an assortment of dangerous scripts in the victim's browser. It requires very little work on the attacker's part to propagate the exploit: the victim doesn't need to click on a malicious link or fall for a phishing scheme, they simply use a trusted site affected by SXSS. Check out the links at the bottom of the page for more details regarding cross-site scripting vulnerabilities.

Code smell: innerHTML and dangerouslySetInnerHTML

A code smell is simply a characteristic in code that indicates a deeper problem. Browsers won't normally run injected scripts automatically, but if a devoloper uses some potentially dangerous browser APIs or element properties, it can lead to a situation where the scripts do run.

Have a look at the following code snippet:

const someHTML = “<h1 id="Hello-world">Hello world</h1>“

const output = document.getElementById("rich-text-output");

output.innerHTML = someHTML

In this example, we store some HTML in a variable, fetch an element from the DOM, and set that element's innerHTML property to the content stored in the variable. The innerHTML property can be used to render HTML from a string inside another HTML element.

What's dangerous about this property is that it will render any HTML or JavaScript you pass into it. That means if someone was able to control the data that was passed into the property, they could technically run any JavaScript in a user's browser.

Another popular but dangerous way to render dynamic HTML in a browser is by using the dangerouslySetInnerHTML React component property. This property behaves exactly the same way as the innerHTML property in vanilla JavaScript and HTML.

The following example appears in the React docs:

const someHTML = “<h1 id="Hello-world">Hello world</h1>“

const output = document.getElementById("rich-text-output");

output.innerHTML = someHTML

If you are currently using either of these properties in a front-end web application, there's a good chance you have some type of cross-site scripting vulnerability. We'll look at how these properties can be exploited and some steps you can take to remediate these issues later in this article.

Code smell: Rich text editors

Another sign that your application might vulnerable to SXSS is simply whether or not you are using a rich text editor, such as TinyMCE or CKEditor.

Your rich text could be a cross-site scripting vulnerability

Your rich text could be a cross-site scripting vulnerability

Most rich text editors work by converting formatted text generated by a user into HTML. As an added security measure, many of these editors employ some form of sanitization to remove potentially malicious JavaScript from their inputs. However, If you are not applying these same sanitization techniques on the services that receive and store the rich text content, then you are likely making your applications vulnerable to SXSS.

Even if you are not rendering the content on your own sites, there's a good chance that this data could be consumed by applications that do render. To design secure applications, it's extremely important that you consider the current and future consumers of your data. If your data is affected by SXSS then so are all the applications that consume your data.

Example application with SXSS vulnerability

Let's take a look at a small example of a web application with an SXSS vulnerability and then attempt to exploit it.

To run this application, first clone this demo app repo and follow the "Running the application" instructions in the readme.md file.

After running the application and going to http://localhost:3000/unsanitized.html, you should see a page that looks like this:

Your rich text could be a cross-site scripting vulnerability

This application simply takes some rich text input from a user, stores it on a web server, and then renders it in the section labeled Output.

Before we exploit the SXSS vulnerability, take a moment to have a look at the application. Refer to the code smells mentioned above and scan through the code to see if you can spot the troublesome sections. Try opening the network tab in your browser and see the requests it sends when you enter and submit some rich text.

In the unsanitzed.html file, you will see the following function, named renderPostByID:

const someHTML = “<h1 id="Hello-world">Hello world</h1>“

const output = document.getElementById("rich-text-output");

output.innerHTML = someHTML

Look carefully at this function. You'll notice that we are using the afformentioned innerHTML property to render some rich text that we fetched from the API in HTML form.

Now that we see the vulnerable portion of the code, let's exploit it. We'll bypass the rich text editor input and hit the API endpoint that saves posts to the web server directly. To do this, you can use the following cURL command:

function createMarkup() {

  return {__html: 'First · Second'};

}

function MyComponent() {

  return <div dangerouslysetinnerhtml="{createMarkup()}"></div>;

}

Notice the data payload we are sending in the request. This is some maliciously crafted HTML that includes an image tag with a onerror property set to some JavaScript that displays an alert dialog. Attackers will use tricks like this to avoid poorly implemented sanitization methods that aim to strip JavaScript from HTML elements before they are stored in a database.

After running the script above, you should receive a post ID like the following:

const renderPostByID = async (id) => {
    // setting url seach params
    let newURL = window.location.protocol + "//" + window.location.host + window.location.pathname + `?post=${id}`;
    window.history.pushState({ path: newURL }, "", newURL);

    // getting rich text by post id
    let response = await fetch(`/unsanitized/${id}`, { method: "GET" });
    let responseJSON = await response.json();
    console.log(responseJSON);

    // rendering rich text
    output.innerHTML = responseJSON.richText;
};

Paste this post ID into the post URL query parameter, and press Enter.

Your rich text could be a cross-site scripting vulnerability

When you do this, you should see an alert dialog on your screen confirming the site is indeed vulnerable to SXSS.

Your rich text could be a cross-site scripting vulnerability

How to prevent SXSS

Now that we have seen how to exploit an SXSS vulnerability, let's take a look at how we can remediate one. To do this, you'll need to sanitize the HTML-based rich text in three different places:

  1. Server side, before the content is stored in your database.
  2. Server side, when the content is retrieved from your database.
  3. Client side, when the content is rendered by the browser.

It might be clear why you want to sanitize the content before storing it in the database and when rendering it on the client side, but why sanitize when retrieving it? Well, let's imagine someone obtains the privileges necessary to insert content directly into your database. They could now directly insert some maliciously crafted HTML, completely bypassing the initial sanitizer. If a consumer of one of your APIs is not also implementing this sanitization on the client side, they could fall victim to the cross-site scripting exploit.

Keep in mind, though, adding sanitization to all three locations could cause performance degradation, so you will need to decide for yourself if you require this level of security. At the very least, you should be sanitizing any data on the client side before rendering dynamic HTML content.

Let's take a look at how we implement sanitization in the secure version of our vulnerable application. Since this application is primarily written using JavaScript, we use the dompurify library for the client side and the isomorphic-dompurify library for server-side sanitization. In the app.js program that acts as our web server, you will find an express endpoint /sanitized with a GET and POST implementation:

const someHTML = “<h1 id="Hello-world">Hello world</h1>“

const output = document.getElementById("rich-text-output");

output.innerHTML = someHTML

In the POST implementation, we first retrieve the rich text from the body of the request and then call the sanitize method of the isomorphic-dompurify library before storing it in our data object. Similarly, in the GET Implementation, we call the same method on the rich text after retrieving it from our data object and before sending it to our consumer.

On the client side, we again use this same method before setting the innerHTML property of our output div in sanitized.html.

function createMarkup() {

  return {__html: 'First · Second'};

}

function MyComponent() {

  return <div dangerouslysetinnerhtml="{createMarkup()}"></div>;

}

Now that you've seen how we properly sanitize HTML to prevent cross-site scripting, go back to the original exploit for this application and run it again, this time using the sanitized endpoint. You should no longer see the alert dialog pop-up, since we are now using the proper techniques to prevent the SXSS vulnerability.

For a full SXSS guide, including best practices and other techniques for preventing XSS, take a look at the OWASP Cross-Site Scripting cheat sheet.

Summary and next steps

In this article, we've looked at how you can increase your application security posture by preventing stored cross-site scripting, a common type of web application vulnerability. You should now be able to recognize whether your own applications are vulnerable, which features you need to review, and how to mitigate before malicious actors can exploit those vulnerabilites.

Security is paramount for enterprise developers. Use the following resources to continue building your awareness of possible vulnerabilities and the ways in which you can improve your security posture.

  • IBM Developer: Security hub
  • OWASP Cross Site Scripting overview
  • Video: Cross-Site Scripting — A 25-Year Threat That Is Still Going Strong

The above is the detailed content of Your rich text could be a cross-site scripting vulnerability. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
JavaScript Applications: From Front-End to Back-EndJavaScript Applications: From Front-End to Back-EndMay 04, 2025 am 12:12 AM

JavaScript can be used for front-end and back-end development. The front-end enhances the user experience through DOM operations, and the back-end handles server tasks through Node.js. 1. Front-end example: Change the content of the web page text. 2. Backend example: Create a Node.js server.

Python vs. JavaScript: Which Language Should You Learn?Python vs. JavaScript: Which Language Should You Learn?May 03, 2025 am 12:10 AM

Choosing Python or JavaScript should be based on career development, learning curve and ecosystem: 1) Career development: Python is suitable for data science and back-end development, while JavaScript is suitable for front-end and full-stack development. 2) Learning curve: Python syntax is concise and suitable for beginners; JavaScript syntax is flexible. 3) Ecosystem: Python has rich scientific computing libraries, and JavaScript has a powerful front-end framework.

JavaScript Frameworks: Powering Modern Web DevelopmentJavaScript Frameworks: Powering Modern Web DevelopmentMay 02, 2025 am 12:04 AM

The power of the JavaScript framework lies in simplifying development, improving user experience and application performance. When choosing a framework, consider: 1. Project size and complexity, 2. Team experience, 3. Ecosystem and community support.

The Relationship Between JavaScript, C  , and BrowsersThe Relationship Between JavaScript, C , and BrowsersMay 01, 2025 am 12:06 AM

Introduction I know you may find it strange, what exactly does JavaScript, C and browser have to do? They seem to be unrelated, but in fact, they play a very important role in modern web development. Today we will discuss the close connection between these three. Through this article, you will learn how JavaScript runs in the browser, the role of C in the browser engine, and how they work together to drive rendering and interaction of web pages. We all know the relationship between JavaScript and browser. JavaScript is the core language of front-end development. It runs directly in the browser, making web pages vivid and interesting. Have you ever wondered why JavaScr

Node.js Streams with TypeScriptNode.js Streams with TypeScriptApr 30, 2025 am 08:22 AM

Node.js excels at efficient I/O, largely thanks to streams. Streams process data incrementally, avoiding memory overload—ideal for large files, network tasks, and real-time applications. Combining streams with TypeScript's type safety creates a powe

Python vs. JavaScript: Performance and Efficiency ConsiderationsPython vs. JavaScript: Performance and Efficiency ConsiderationsApr 30, 2025 am 12:08 AM

The differences in performance and efficiency between Python and JavaScript are mainly reflected in: 1) As an interpreted language, Python runs slowly but has high development efficiency and is suitable for rapid prototype development; 2) JavaScript is limited to single thread in the browser, but multi-threading and asynchronous I/O can be used to improve performance in Node.js, and both have advantages in actual projects.

The Origins of JavaScript: Exploring Its Implementation LanguageThe Origins of JavaScript: Exploring Its Implementation LanguageApr 29, 2025 am 12:51 AM

JavaScript originated in 1995 and was created by Brandon Ike, and realized the language into C. 1.C language provides high performance and system-level programming capabilities for JavaScript. 2. JavaScript's memory management and performance optimization rely on C language. 3. The cross-platform feature of C language helps JavaScript run efficiently on different operating systems.

Behind the Scenes: What Language Powers JavaScript?Behind the Scenes: What Language Powers JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript runs in browsers and Node.js environments and relies on the JavaScript engine to parse and execute code. 1) Generate abstract syntax tree (AST) in the parsing stage; 2) convert AST into bytecode or machine code in the compilation stage; 3) execute the compiled code in the execution stage.

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

WebStorm Mac version

WebStorm Mac version

Useful JavaScript development tools

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Integrate Eclipse with SAP NetWeaver application server.

MantisBT

MantisBT

Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.