Update/TL;DR: If you didn’t generate it, assume it’s malicious.

Sometimes, the state of your website’s security can be affected by resources and services outside your control. The topic of today? Browser extensions.

Recently, we disclosed a vulnerability to a well-known company (let’s refer to them as Company) in their browser extension (specifically, in Chrome). To their credit, Company responded rapidly and fixed the issue within 2 days – major props to them for responding so quickly. Before we get into the vulnerability, let’s talk a little about what the extension does.

Chrome extensions? Not much harm can come from that…

From the Company’s feature description, the browser extension automatically takes content you want to share and pops it into a message, ready to go. This sounds great! And for the most part, it is. Occasionally, though, we are reminded that extensions can be dangerous, and now is one of those times. Browser extensions are effectively allowed to run any arbitrary JavaScript they’d like on any page you visit, changing the DOM at will. In Company’s case, they were trying to make their customers’ lives easier by finding any text that looked like a Twitter hashtag or Twitter handle and converting it into a clickable link that automatically searches for that hashtag or handle. Well-intentioned, as is most of what we do as software engineers.

Sounds great. So where’s the vulnerability?

The vulnerability boils down to the following: if a page had a hashtag in its content that, as part of the hashtag, had an HTML-escaped element appended, it would get unescaped and then, by definition, inserted directly into the DOM by Company’s browser extension. Consider the following example:

If this showed up in the text of a page, the extension noticed the #tinfoil hashtag and attempted to convert it into a link. So the above became something like the following:

<a class="_company_extension" a="" href="#" #tinfoil"="" "javascript:var e = document.createEvent("CustomEvent"); e.initCustomEvent("extensionEvent", true, true, {type: "hash", value: "#tinfoil"}); document.body.dispatchEvent(e); return false;">#tinfoil</a><script>alert('XSS'​)</script>

You’ll notice that this is malformed HTML to begin with (the #tinfoil attribute of the a tag, for example), but the more important issue is that the Company’s browser extension actually unescapes the escaped HTML for the script tag and, in doing so, inserts it into the DOM. Of course, the link still works, pulling up a search for #tinfoil as it should, so if we weren’t popping up an alert box, the user would be none the wiser.

Uh oh…so what does this mean for me?

Well, effectively this means that if you were using the Company’s browser extension within Chrome, it would execute any malicious JS stored on any website you were visiting. For example, suppose you were logged into a financial service and viewing the discussion forums for help on a topic – someone could have posted this malicious hashtag in response creating a persistent, or stored, XSS. Worse is that even if said financial service had taken the proper precautions to prevent XSS by escaping HTML into its HTML entities (as we recommend), the Company’s browser extension would still have re-encoded it and run the malicious JavaScript. Essentially, Company had accidentally XSS’d the whole internet.

User generated content occurs everywhere, and it is always important to escape any input you may receive. The mantra we often use is: “If you didn’t generate it, assume it’s malicious.” In this case, the document returned by the website the user is visiting should be considered potentially malicious. The extension is acting on data it did not create, and as such should treat it more carefully than it would other data, by escaping everything it can. So if you’re building a browser extension, be sure to treat the DOM of whatever page you’re modifying as dangerous, because it is.