Your 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>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>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.
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:
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>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()} />; }
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.
When you do this, you should see an alert dialog on your screen confirming the site is indeed vulnerable to SXSS.
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:
- Server side, before the content is stored in your database.
- Server side, when the content is retrieved from your database.
- 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>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()} />; }
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!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

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

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

Discussion on the realization of parallax scrolling and element animation effects in this article will explore how to achieve similar to Shiseido official website (https://www.shiseido.co.jp/sb/wonderland/)...

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

Learning JavaScript is not difficult, but it is challenging. 1) Understand basic concepts such as variables, data types, functions, etc. 2) Master asynchronous programming and implement it through event loops. 3) Use DOM operations and Promise to handle asynchronous requests. 4) Avoid common mistakes and use debugging techniques. 5) Optimize performance and follow best practices.

How to merge array elements with the same ID into one object in JavaScript? When processing data, we often encounter the need to have the same ID...

Explore the implementation of panel drag and drop adjustment function similar to VSCode in the front-end. In front-end development, how to implement VSCode similar to VSCode...
