Finding XSS exploits in CTFs is fun, but nothing can match the exhilaration of discovering a vulnerability in a real, live app. To help you get a taste of that excitement, and maybe some advice for bug hunting that you may find useful, I’m going to open up an app I use every day and just see if I can find some interesting exploits.
Probably my favorite web app, is the Groups app on Urbit, a peer-to-peer chat app. It looks like this:
And thanks the the fantastic community and very welcoming developer community, this app is a joy to hack without fear. We won’t worry about the fancy p2p backend since that is far too complicated to dive into here. So let’s focus on frontend bugs. Luckily, the source code for Groups is available on Github, so let’s see what kind of tech stack we’re dealing with.
Finding XSS with source code
Looking at https://github.com/tloncorp/landscape-app, we see we’re working with a React app. There are three common ways for pentesters to find XSS when dealing with React:
- Anchor tags (ie links) that allow the user to set a
javascript:
pseudo-url. - Using
dangerouslySetInnerHTML
to render user-controlled content unsafely. - User controlled props (usually via JSON, allowing users to set
dangerouslySetInnerHTML
).
Let’s start at the top and see what we can do. Right off the bat, we see that we can set links in the chat. What happens if we try to supply javascript:alert(1)
as our link?
zNo dice – we are disallowed. The “Done” button appears grayed out, preventing us from submitting our malicious link But this is the frontend disallowing us. What if we post a legitimate link (to pacify the UI), then intercept the request with Burp and change the link to a malicious one?
First, we’ll submit a “normal” link, which we will intercept with Burp:
Now let’s look in our proxy where we’ve intercepted the request in Burp and try to alter the URL of the link in transit…
Notice the part at the very bottom, where is shows our Javascript URL. Okay, now we can forwarding the request and checking the UI, to see what happens. We’ll go to the same group where we sent this message, and then click the malicious link.
And…we get a whole lot of nothing. A blank new tab opens, and it doesn’t even execute our Javascript code. What’s the problem? I can explain. Sadly (for us!), when a link opens in a new tab, javascript psuedo-url’s are blocked by modern browsers.
Except, what if the link didn’t have to open in a new tab? Maybe there’s a way we can get this link to execute in the same tab, if we’re just a little tricky.
Finding a workaround for our exploit
You know how when you click on something while holding the CTRL key (or Command key on Mac), it opens in a new tab? Or maybe you have a middle mouse button that does it. Well, if someone just so happens to click our link that way, the XSS actually works!
Cool! So maybe it’s not as impactful as a normal XSS, but it’s definitely something!
Still, I wonder why trying to open the link in a new tab causes the JS to run in our current tab? After a long search for the answer to this question, it’s still not very clear to me. I even tried grokking Chrome’s Byzantine source code. Still, whether we understand its workings or not, this infamous trick has led to many a bug bounty.
Anyway, it’s easy to imagine creating a phishing scenario where we convince someone to open such a link with CTRL click. What can we do with this, though? The impact is less severe because the user has to click in this odd way, but what is the impact?
Well, you can steal the session cookie and access someone else’s ship!
With this, we can simply change the urbauth cookie when accessing tlon.network and have this same interface, but on their ship!
Now that we’ve found the vuln, we can think of a fix to suggest to the devs. Nothing fancy, just ideas for things that would address this issue.
Finding XSS and fixing it!
We won’t go as far as submitting a patch, but we can at least proffer changes that that seem necessary to make the app safe from this kind of attack. There are three basic issues that this vuln makes apparent.
- Disallow javascript: pseudo-url’s in links
The frontend is already doing its part. The backend should reciprocate by verifying that whatever the frontend sends is correct. The backend shouldn’t trust the frontend, because the user can control the frontend to do whatever they want.
- Setup a proper Content Security Policy
A Content Security Policy defines the valid sources from which Javascript code can execute. It also does a bunch of other important stuff. But if this app’s CSP didn’t allow JS code to run from insecure sources, this attack would have been blocked by the browser.
You can learn more about CSPs from Mozilla‘s excellent docs, here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- Set the HttpOnly attribute on the
When an XSS attack occurs, it can attempt to steal your cookies via the document.cookie variable. However, many cookies are only sent between the browser and the backend, and Javascript should not have access to these cookies for any valid reason. In such cases, the HttpOnly attribute exists to prevent XSS from stealing your cookie.
Finding XSS is easy
You might be afraid to hack into apps. By all means, you should make sure that there is a bug bounty program or something else in place allowing pentesters to play with the app’s security. But I am always pleasantly surprised: most app devs are grateful when you ask if you can look for vulns in their app.
Niche and upcoming technologies often don’t have the time and resources to aggressively harden their security. By playing around and hunting security bugs, you are often doing a real service to an app or community you care about. So go out there, experiment with inputs, and have fun hacking!
Leave a Reply