One of the best vuln write-ups I've read in a while, in that it steps you through how the initial entrypoint was found, and the steps needed to turn that into a dangerous exploit.
I think what really makes this writeup worth the read is the insight it shows into the thought process of identifying an interesting bug and weaponizing it. Thanks Frans!
The traditional way to do this with good cross browser support is to use a DOM element. As long as it's not added to -the- DOM (page) the overhead is quite small. When you think about it, creating DOM nodes is pretty much one of the core things for a browser to do, so there's been a lot of optimizations for those things over the years.
That doesn't mean that it's pretty. But neither is the DOM API.
Nope, you would have to build your own, which is fraught with potential vulnerabilities. While roundabout, this does send you through the browser's own URL parsing and validation, which obviously should be secure and well-tested.
evt.origin is already provided by the browser, though. It shouldn't need canonicalization, unless this is some kind of browser compatibility workaround.
It's not canonicalization, it's extracting the hostname from the origin (which contains the protocol and port info as well) without implementing your own URL parsing.
There is a new URL api but it's still "experimental" and not supported at all by IE. This new API is just a different interface to the url parsing logic already used for the location and href objects so you can use those instead and get the same functionality.
A tag elements expose this with the href attribute and it's very lightweight since it's not inserted into the DOM. Create one reusable global element and make a wrapper function around it if you want to be even more efficient.
My suspicion is that creating the <a> tag may be more secure because it's implemented at the browser level, below any Javascript which may be running. Probably also more performant. Not sure though.
There's a node module, but this is a quick hack to do it. Relatively low cost if this function isn't getting called too much. Combining this + some basic memoization would probably be the easiest and most performant solution. I imagine you're usually at most receiving events from a single origin.
That last line is killing my internal JS linter. No reason not to make it ===, right? Also, inconsistent semi-colon usage in the code he showed in the post.
Just out of curiosity, how long did it take for you to come up with this PoC? From the initial notice that something might be exploitable until you sent the email to slack?
Your post makes it look so easy, but it would surely take weeks for me to figure out all these things.
It's a common pitfall and easy to look for. The stuff I spent most time with regarding this specific issue was finding the proper event that did something bad.
Personally, my first clue is that token protection/revocation is rarely implemented properly. It's one of the first things I look at when dealing with a web-based system which is obviously using them.
I think what really makes this writeup worth the read is the insight it shows into the thought process of identifying an interesting bug and weaponizing it. Thanks Frans!