2
votes

SPOILER ALERT: This question contains an answer to one of the problems from the Google's XSS Challenge! Please stop reading further if you're not interested in knowing the answer right now.


I'm able to get pass the level 4 of the challenge, however, I still don't know how exactly the exploit is working. The following is the code from Google's XSS challenge - Level 4:

<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />

    <script>
      function startTimer(seconds) {
        seconds = parseInt(seconds) || 3;
        setTimeout(function() { 
          window.confirm("Time is up!");
          window.history.back();
        }, seconds * 1000);
      }
    </script>
  </head>
  <body id="level4">
    <img src="/static/logos/level4.png" />
    <br>
    <img src="/static/loading.gif" onload="startTimer('{{ timer }}');" />
    <br>
    <div id="message">Your timer will execute in {{ timer }} seconds.</div>
  </body>
</html>

Basically, they are using Django framework (which uses a bunch of security measure against XSS). The variable timer carries the input from the user. The goal of this activity is to alert a message by sending a payload which can bypass Django's XSS security.

I'm able to alert a message using one of the following payloads:

');alert('xss

OR

3') || alert('1

I'm able to clear the level using the above payloads but I'm still not sure where exactly the alert() method is being called? In the onload handler OR within the startTimer() method?

I'm confused because if I check the source HTML of the page after submitting the payload, Django is encoding the payload:

<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />

    <script>
      function startTimer(seconds) {
        seconds = parseInt(seconds) || 3;
        setTimeout(function() { 
          window.confirm("Time is up!");
          window.history.back();
        }, seconds * 1000);
      }
    </script>
  </head>
  <body id="level4">
    <img src="/static/logos/level4.png" />
    <br>
    <img src="/static/loading.gif" onload="startTimer('&#39;);alert(&#39;xss');" />
    <br>
    <div id="message">Your timer will execute in &#39;);alert(&#39;xss seconds.</div>
  </body>
</html>
2

2 Answers

4
votes

What seems to be confusing you is the mix of two different languages: HTML and JavaScript. &#39; is HTML. It's translated to the ' character when displayed and when interpreted as JavaScript. That means, from the JavaScript interpreter's point of view, there's no difference between ' and &#39;. The code onload="startTimer('&#39;);alert(&#39;xss');" is effectively the same as onload="startTimer('');alert('xss');" even though at the first glance it looks like something that shouldn't work.

3
votes

I see why the XSS worked! One of the section in the OWASP XSS cheat sheet says:

HTML entity encoding is okay for untrusted data that you put in the body of the HTML document, such as inside a tag. It even sort of works for untrusted data that goes into attributes, particularly if you're religious about using quotes around your attributes. But HTML entity encoding doesn't work if you're putting untrusted data inside a tag anywhere, or an event handler attribute like onmouseover, or inside CSS, or in a URL. So even if you use an HTML entity encoding method everywhere, you are still most likely vulnerable to XSS. You MUST use the escape syntax for the part of the HTML document you're putting untrusted data into. That's what the rules below are all about.

In this case, the user input is being fed into an event handler, which will treat it as a JS instead of HTML. And, the input is being escaped in HTML context (not in JS context). Therefore, JS will treat startTimer('3&#39;) || alert(&#39;1'); as startTimer('') || alert('1'); and will simply run this script.

PS: JS escaping might have prevented the attack.