In 2015, Daniel LeCheminant identified a Cross-Site Scripting issue in Hackerone, which came through injecting a fake React component (being parsed through JSON) into a React* application.
React previously identified their generated elements by using a simple “_isReactElement” property within the object (which was set to true), but trusting the user supplied input as a JSON object, which led to the vulnerability in the website, was a fault with the web page logic that was soon fixed by Hackerone.
Unfortunately, security can be hard, and it is often pertinent to look through previous research and the associated fixes to understand how to mitigate security weaknesses.
The issue discovered by Daniel was later addressed within React, by changing the property to use a Symbol, therefore mitigating the issue, since Symbols cannot be controlled through a JSON object. However, it is important to remember that React is a framework that is supposed to be used generally across all browsers, and therefore has to be able to work with older browsers as well. Since Symbol objects are a feature defined in ES2015, and the feature was never supported for Internet Explorer, React defaults to using numbers to determine the type instead of Symbols, which can still be controlled through JSON objects. Although all versions of Internet Explorer only have a 5.1% browser share (as of September 2019), this still means that 5.1% of your users may be vulnerable to XSS attacks. This limitation of Internet Explorer can be mitigated by using polyfills for Internet Explorer, but the solution seems rather arbitrary, especially if your own code is not reliant upon ES2015 code. Therefore, it is still important to understand the underlying problem of the code:
Trusting user-controlled objects
Even with React’s mitigation in place, your own code may still be susceptible to XSS via user-controlled objects if a user has control over other properties within a React object, by way of setting the dangerouslysetinnerhtml property. Therefore, it is best to not rely upon the underlying framework to save you.
Protecting against people inserting unexpected objects can be especially tricky in dynamically-typed languages, but this problem is applicable to many technologies used throughout applications new and old including:
- binary serialization
The mechanism to protect against these different ways a malicious user might inject objects differs depending upon the technology in use, whether configuring how checking of the object occurs (such as the example of React), configuring deserializers to only deserialize certain types, or simply removing the functionality and finding a safer way to do the same thing (in the scenario that there is no safe way).
In the end, different frameworks will give you different security capabilities. Some will give you free reign, and others will try to protect you more, making it harder to introduce vulnerabilities, but it is you, the developer, who needs to take responsibility to not rely upon the framework or underlying technology to protect you.
Do not trust user-controlled input. Make sure it is safe regardless of any frameworks or libraries that you may be using, so you free yourself up to change dependencies if needed, and you can relieve yourself from being concerned with bugs** in the framework.
*Micro Focus Fortify Secure Coding Rulepacks support scanning React applications from 2019 Update 3
**in the example above, it is rather questionable whether this is a true bug, though the mitigation in the framework is appreciated.