We entered the hackathon as sponsors but I decided to give it a shot as well.
Earlier in March, EthGlobal had a weekend NFT hackathon. Participants had a few days to spin up a proof of concept and we would be part of the sponsors as Charged Particles giving support in Discord to people building on our NFT protocol.
Now let’s take a look at their website.
I quickly dumped the html+css with Charles Proxy and went ahead to look at how the user data is treated along the way.
The only interesting piece to work with resided in chat.js
(Link) as the sources are not minified we notice a lot of template management for building the HTML looking “hand crafted” which is generally a good lead.
Failures and injections from templating functions are not unheard of, even for our dear underscore.js
Secondly we notice a lot of calls to Firebase Firestore which I was familiar with.
Turns out the website allows you to register an account to be able to chat:
After some quick recon to figure out where there fields are served back to us in the HTML code, namely the user name, prepending chat lines:
Which translates into the following html:
Most of the template data to be inserted looked like it was protected thanks to DomPurify which is supposed to be an XSS fortress.
Fair enough c-name
looks strong here and sliced to 50
chars, outch. We take a mental note that it’s added then to window.messages
that is then sorted by time. This window.messages
is actually a custom List()
implement by https://listjs.com/ 1.5.0
The problem arises with the updateMessages()
function. It is called from this part of chat.js which triggers at page load:
What does updateMessages do?
.values()
is actually a DOM modifier according to https://listjs.com/docs/item-api/ so we have potential injection.
After a lot of experimenting with what’s allowed or not, we use the registration POST URL to register our malicious user (MU) with a name of:
<img src onerror=console.log(1)/>
And then while logged as MU, we post a message in the chat so it triggers updateMessages
..for everyone live on the page, and sure enough if you were to open your console at home on EthGlobal while I was hacking it in the background, you would have seen…
The problem is now, anything we enter in the name that’s > 50 chars
gets rejected by the backend server. Firestore simply returns blank for our user name payload.
Thankfully we’ve full of experience since https://elyx0.medium.com/xss-dangers-stealing-top-players-accounts-fad1f52b7a66 and we know there is strength in numbers.
So what do you do when you can only input 50 chars into a payload per user… well… you build your new payload bit by bit using multiple users with each name a bit of the payload, so their chat messages payloads chunks will trigger the final payload.
dd.ngrok.io
would then load my custom payload, as lengthy as I’d like.
And what they saw doing so:
It was easier to see how this could escalate in an abuse of MM or embedding web3.js ourselves into the page and prompt the UI for “Special NFT Hack giveaway, 1ETH to enter, no loss lottery 🎉” or some kind of setApprovalForAll
and steal all their ETH & NFTs at a later date.
You could also try to get the team IPs so the MM never gets prompted for them while you run the attack… but that’s beyond the scope of this article.
Anticlimactically I was rewarded with… nothing besides the sanitizing function now being at my name… so much for white hacking nowadays.
ETHGlobal is currently running #ETHOnline, good luck to all!