A Silly Trick I Learned During Stripe CTF

I recently participated in Stripe Capture the Flag and after completing six of the eight levels I learned a fair bit about browser security and a few server-side attacks. It was an extremely valuable experience not simply because I learned a few tricks, but because It renwed my awareness of security holes. Of the things I've learned, the following Javascript 'trick' was probably the most amusing / my favorite.

Level 6 of the challenge takes place on a server that hosts a sort of social network. Users can log in and post things to a public 'Wall'. There is another user on the system that logs in to check on recent posts approximately once every five minutes. There is also a page on the application which will show the current user's password in plain text (!).

My first approach was to use XSS to ebmed a harvesting script in a post. The script would make an XHR request to the page that shows the current user's password and post it back to the wall automatically, leaving a landmine for the other user. Getting anything executable on the page involved some breaking parsing on the JSON string embedded in the page that showed the recent posts, and a single tag in any message body will do that. At that point, the various client-side encoding / filtering that happens when a new message is posted cannot run.

I thought I was home-free, but it turns out the server rejects any posts with a single or double quote in the body or title. Writing Javascript that will request a URL and parse a bit of the DOM without ant quotes is...hard. Loading a script from a 3rd party server to do this work wouldn't work because of the Same-Origin Policy. These scripts wouldn't have access to the current user's cookies when requesting the password page.

I had to figure out a way to embed the executable script in a post without any quotes...after a bit of brain wracking over encoding quotes, I realized the Javascript String prototype has a useful method called 'fromCharCode' that will take an arbitrary number of unicode character codes and produce a string that is the concatenation of their ASCII representations. This was my way in. So I converted all my code to a string of the form "eval(String.fromCharCode(....))" that sucessfully posted and harvested the victim's password. The fact that this is possible and that the challenge called for it was kind of awesome, so here's the ridiculously simple helper function that got me to Level 7.

Stripe really created something great with this competition. I feel much more confident in my understanding of the subtle ways in which browsers protect users from malicious content and that my own code is (hopefully!) better protected than some of these challenge's sites! ;)

Thanks, Stripe. It was fun.