Cross-site scripting (XSS) attacks are very serious. When fully exploited, it gives one full control of user account on websites. Imagine somebody gaining access to your bank account and transferring all the money out. From the bank’s perspective, this would be a perfectly fine legitimate operation from the account owner.
Let’s consider a series of steps, each of which seem reasonable, but in the end leads to a XSS attack. The story begins with a large single page application and we’re implementing a new feature, user profile.
Surely we don’t want new users on our landing pages to have to load the user profile? Just a few hundred milliseconds will cost us millions of dollars.
Let’s separate the user profile into its own microapp. We don’t need to be bogged down by our main app.
Aside: There is a lot of “micro-frontend” lingo floating around and there is little agreement on what it even is. For the purposes of this story, microapp is an independent single page application. It has its own
On face value, step 1 is very reasonable. Microapps reduce deployment risk being an independent single-page application. The increased delay with a real page load is acceptable when we’re switching context as such with user profile.
The rhetoric laid on thick here because this argument is insidious. Frameworks exist beyond to speed up development, but also to allow everybody to benefit from security expertise they may not have.
One doesn’t even need to think about XSS in React, as long as they avoid dangerouslySetInnerHTML. The reason why a real attacker needs to advocate against frameworks like React is because they have done such a good job with APIs like dangerouslySetInnerHTML. These scary looking APIs are easy to spot in pull requests or automatically with linters.
No, no! Don’t worry about XSS. We’ll implement our own mitigation! Look, we’ve property implemented escaping of user input when modifying the DOM.
At first glance, the code is very reasonable. But there are 2 possible XSS vectors, do you see them?
escape function is fine. It is essentially what setting
textContent would be. While attribute value and children have been properly escaped, the tag and attribute name have not been. How would an attacker exploit this?
Attacking the tag is unlikely, as there is little real-world reason on why HTML tags would be dynamic, however the attribute name can be abused with data attributes.
Surely, we would want user data to be visible as data attributes. This would speed up development as it would be trivial to see the state of the app by simply looking at the DOM tree in dev tools! We wouldn’t need to implement a custom API to extract data for end-to-end test automation. It could just read the data directly off data attributes. It could even handle custom field for the user profile.
This may seem harmless, but now that custom fields is user input that can end up in attribute name, we have a XSS vector. Now with a custom field name that closes attribute and
div, it can insert an
img with an
scripttags are not run by design. This is why we need to use this awkward
See full source and demo.
In the real-world, the author of step 3 may not be the same person as the author of step 4. Steps 1 through 3 could be completely innocent with good intentions. This is why preventing XSS requires vigilance beyond “just use a framework lol”. We need to understand how frameworks protect us and why we should shut down the rock star developer who wants to forgo the framework.
Do you want to prevent XSS attacks? You’re in luck, Battlefy is hiring.