3

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>
Rahil Arora
  • 4,259
  • 2
  • 23
  • 41

4 Answers4

1

What happens is that the HTML output is:

<img src="/static/loading.gif" onload="startTimer('{{ timer }}');" />

{{ timer }} is a variable which gets "translated" to whatever the ?timer GET parameter is. So suppose we have a legit parameter: ?timer=3. It gets "translated" to:

<img src="/static/loading.gif" onload="startTimer('3');" />

But what if you put some code in timer? 3');alert('xss');//

<img src="/static/loading.gif" onload="startTimer('3');alert('xss');//')" />

As you can see, the logo loads, and once it loads, the onload callback is executed. There's the vulnerability.

P.S. it's not in startTimer itself, because it parseInts the variable, and it removes everything but numbers, essentially 3'); ... becoming in 3.

rev
  • 111
  • 3
  • But the HTML source code shows that it's `onload="startTimer('');alert('xss');"` which means `');alert('xss` is passed to `startTimer` as a string. – Rahil Arora Jun 09 '14 at 16:16
  • 1
    @RahilArora No, it’s not. The actual attribute value of *onload* would be `startTimer('');alert('xss');` as the `'` are replaced by the character `'` they are referring to. – Gumbo Jun 09 '14 at 16:32
  • @Gumbo: So, why do we encode them if they are being replaced by the browser with the actual chars? – Rahil Arora Jun 09 '14 at 16:46
  • @RahilArora Django encodes them because you could also have used `attr='{{ timer }}'`. – Gumbo Jun 09 '14 at 16:48
  • Ok. But we encode something so that the browser cannot execute that particular code, right? Then why is still executing the part which is encoding? – Rahil Arora Jun 09 '14 at 17:13
  • 1
    @RahilArora No. Django only escapes certain characters that are special in HTML as they are used to delimit certain parts that represent a context in which the containing data is treated differently. Like quoted HTML attribute values. However, Django doesn’t know whether these parts are interpreted as something other than HTML, like the attribute value of *onclick* is interpreted as JavaScript. – Gumbo Jun 09 '14 at 17:23
1

The problem is that strings inside HTML attribute values interpret &#39; as equivalent to '.

So while your smart escaping turns the input 3') || alert('1 into 3&#39;) || alert(&#39;1, so the img element in the page becomes:

<img src="./test_files/loading.gif" onload="startTimer('3&#39;) || alert(&#39;1');">

your browser treats this as equivalent to:

<img src="./test_files/loading.gif" onload="startTimer('3') || alert('1');">

and the XSS works.

The reason it does this is so when you want to say have <img src="image" alt="O&#39;Malley"> the alt text reads as O'Malley.

dr jimbob
  • 38,768
  • 8
  • 92
  • 161
  • Attribute *tags*? – Gumbo Jun 09 '14 at 16:33
  • So, does that mean the XSS prevention using output encoding will not work if a user input is directly used as a part of an HTML attribute? – Rahil Arora Jun 09 '14 at 16:37
  • @RahilArora - XSS prevention will prevent you from escaping out of the attribute tag. E.g., if you had `` you wouldn't be able to escape to get to ``. The problem in this case is inserting untrusted input into the middle of a JS function in an HTML attribute. – dr jimbob Jun 09 '14 at 16:43
  • Ok. But we encode something so that the browser cannot execute that particular code, right? Then why is still executing the part which is encoding? – Rahil Arora Jun 09 '14 at 17:15
  • 2
    @RahilArora The output is escaped to be in HTML context, not in JavaScript context. To use that value in javascript, the quotes should be replaced with \', not HTML entities. – David Jun 09 '14 at 17:25
  • @David: Oh! That makes sense now. It was really confusing. Mostly because there's no good resource/documentation on how exactly this works. – Rahil Arora Jun 09 '14 at 17:30
  • @RahilArora Actually, you need both: first encode for JavaScript string and then for HTML attribute value. So in the end you should have something like `onload="startTimer('\');alert(\'xss');"`. – Gumbo Jun 09 '14 at 17:53
1

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.

Rahil Arora
  • 4,259
  • 2
  • 23
  • 41
0

Your alert is executing right in the onload because alert cant be a parameter value. Like this it gets executed instantly.

Xatenev
  • 231
  • 1
  • 6
  • But it can reach the method as a param string and become a part of the statement in the method. Like: `seconds = parseInt('3') || alert('1') || 3` – Rahil Arora Jun 09 '14 at 15:48