At Bridewell, our penetration testing engagements are focused assessments of the security stance of their software, hardware, or device configurations. These include manual testing to simulate the capabilities of real world attackers in order to identify vulnerabilities and to determine the impacts of exploitation. We work closely with our clients to inform them of the issues we uncover, and to advise them on remediation steps and secure approaches, tailored to their unique needs.
As you can imagine, the findings we report are typically very specific to the client and their unique recipes of code and infrastructure. However, every so often, we unearth flaws in common libraries or publicly available code that the client has used in their products. When this happens the issue could well affect a wider audience, including other companies and individuals who are using the same code in their products or projects, who may also be vulnerable to attack.
This finding of new flaws in commercial and open-source code is known in the security industry as a zero-day. In this blog, we will tell a tale of a zero-day uncovered in commonly installed code used on websites built using React.
How Did We Discover CVE-2023-50615
In December, I was assessing an application for a client which included functionality to input names and descriptions of data into the website. In order to make this text appealing and readable, the client had opted to use a WYSIWYG (What You See Is What You Get) HTML Editor plugin - a piece of code that allows you to add 'Rich Text' styling to your content to make it bold, italic, change font styles and colour, etc. The plugin in use - react-draft-wysiwyg - is a well-established project, used on hundreds of thousands of websites via download from the npm online package manager.
Figure 1 - react-draft-wysiwyg HTML Editor with minimal functions enabled
Usually when assessing web applications, the presence of well-known and well-tested plugins means that there are less likely to be security issues, compared to custom code written for the application. Regardless, we always take a thorough approach to testing and robustly test even known-good configurations for potential issues by following our custom testing methodology.
Now one of the critical things to check for in web-based text editor tools is which HTML tags can be assigned to the user-supplied text. For example, the <em> tag makes text emphasised (or italicised), and is safe to use. however, allowing the inclusion of the HTML <script> tag, for example, can allow the introduction of JavaScript code into the application, which an attacker can use to hijack accounts of logged-in users or perform other malicious actions.
Initial tests showed that the react-draft-wysiwyg plugin was doing the expected checks when saving content. This is done to prevent common JavaScript injections as well as defend against the fairly complex payloads that attackers use to bypass other injection protections.
In all honesty it's times like this when it's really easy to assume that if the basic defences are in place, and the code is well-established and well-known, that there is no point in digging deeper. After all, if no-one has previously seen issues in this code there's little chance of finding any new issues. However, in this job it’s vital to pay good attention to detail, and over time this leads to a weird 'sixth sense' you get for things that don't seem quite right.
And something didn't seem right. The way the code was being escaped and encoded in an overlapping pattern seemed slightly inconsistent, and that piqued my interest.
What Was the Vulnerability?
A number of HTML tags were accepted but, in all cases, only seemed to allow approved attributes to be assigned which safely prevents code injection. I took a methodical approach and tested each HTML tag using a custom script I wrote. oor each valid tag,I checked which attributes were allowed. Everything seemed safe - except one tag.
The iframe tag is used to embed external content into a webpage. Sometimes this is custom content and other times embedding pages from external sites. The html-to-draftjs 'sister' plugin that works alongside the editor allowed this tag and its attributes to be saved. By specifying the 'src' attribute and setting its value to a JavaScript protocol command, it was possible to insert code into the user-supplied content.
Upon reloading the editor page, the react-draft-wysiwyg plugin rendered the supplied content and triggered the JavaScript to execute. So that's not good!
Checking the package.json file shows the plugins are fully up-to-date, which suggests a new vulnerability. (We will later build a test application to demonstrate this.)
Now the question is, "what we can do with it?"
What is Cross-Site Scripting (XSS)?
Cross-Site Scripting (XSS) is a common attack deployed against users of a web application. It can lead to stealing confidential user tokens, application credentials, or other sensitive data from the user's logged-in session. It can also be used to hijack functionality tied to any affected users.
In our case, we were able to perform this attack as a low-privileged application user in a way that could trigger the code to run in the context of an Admin of the site. Through this route, we then called the admin API as a privileged user. This API could be used to extract sensitive data about application users, reset user passwords, and modify user privileges.
We reported the issue to the client and gave them remediation advice so they could lock down this issue.
Where do we go from here? Well this plugin is used in other applications, and may present vulnerable configurations on many sites. For the attack to be viable, the application would have to allow low privilege users to create content, so not all sites with the plugin installed will be vulnerable, but this is a likely scenario for many applications.
Figure 2 - demo application showing XSS on latest versions of html-to-draftjs and react-draft-wysiwyg
Having validated the issue is present in the code base for the plugin via building a demo application, I then reached out to the plugin developer by a number of routes. Sadly, no responses were received over a period of 6 months and the code has not been fixed, suggesting that the plugin is not being actively supported.
CVE-2023-50615 has been registered for this issue, affecting html-to-draftjs versions up to v1.5.0 (https://www.npmjs.com/package/html-to-draftjs).
How Can You Fix This Issue if You Are Using These Plugins?
If you have a need for these plugins specifically, you could consider adding custom code to patch this specific issue or perhaps implementing an HTML sanitiser into the workflow, such as DOMPurify.
However, as the plugin has not been fixed 6 months after the initial report, it is probably best to migrate your React app to a different HTML Editor which is being actively supported.