美文网首页
RingZer0-CTF JavascriptChallenge

RingZer0-CTF JavascriptChallenge

作者: byc_404 | 来源:发表于2020-07-22 11:26 被阅读0次

    在网上无意间看见一个CTF 解题平台,感觉里面的分类还挺有意思的。于是挑了一个javascript challenge来做一下。最近做题时经常感慨js的很多弱类型特性远比php丰富的多了,所以借此机会简单接触下js的魔鬼代码

    0x01

    首先js类型的题目必然是在静态页面就可以找到源码的。有时会加混淆,但混淆的机理其实基本上都挺一般的。这里直接查看页面源码

    // Look's like weak JavaScript auth script :)
    $(".c_submit").click(function(event) {
        event.preventDefault()
        var u = $("#cuser").val();
        var p = $("#cpass").val();
        if(u == "admin" && p == String.fromCharCode(74,97,118,97,83,99,114,105,112,116,73,115,83,101,99,117,114,101)) {
            if(document.location.href.indexOf("?p=") == -1) {   
                document.location = document.location.href + "?p=" + p;
            }
        } else {
            $("#cresponse").html("<div class='alert alert-danger'>Wrong password sorry.</div>");
        }
    });
    

    发现一个用户密码的判断。成功登陆即可完成事件。那么我们把密码扔进console转换得到JavaScriptIsSecure.登陆拿flag.

    0x02

    // Look's like weak JavaScript auth script :)                                                                          
    $(".c_submit").click(function(event) {
        event.preventDefault();
        var p = $("#cpass").val();
        if(Sha1.hash(p) == "b89356ff6151527e89c4f3e3d30c8e6586c63962") {
            if(document.location.href.indexOf("?p=") == -1) {   
                document.location = document.location.href + "?p=" + p;
            }
        } else {
            $("#cresponse").html("<div class='alert alert-danger'>Wrong password sorry.</div>");
        }
    });
    

    这里直接用明确的算法对密码进行加密然后与hash值进行比较。不过注意的是php里常见的0与0e的弱类型比较在js中是不会出现的(但是可能会出现其他弱类型问题)

    所以直接去网上找得到adminz弱密码。

    0x03

    一个典型的混淆。同样也是典型的解法。

    var _0xc360=["\x76\x61\x6C","\x23\x63\x70\x61\x73\x73","\x61\x6C\x6B\x33","\x30\x32\x6C\x31","\x3F\x70\x3D","\x69\x6E\x64\x65\x78\x4F\x66","\x68\x72\x65\x66","\x6C\x6F\x63\x61\x74\x69\x6F\x6E","\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x27\x65\x72\x72\x6F\x72\x27\x3E\x57\x72\x6F\x6E\x67\x20\x70\x61\x73\x73\x77\x6F\x72\x64\x20\x73\x6F\x72\x72\x79\x2E\x3C\x2F\x64\x69\x76\x3E","\x68\x74\x6D\x6C","\x23\x63\x72\x65\x73\x70\x6F\x6E\x73\x65","\x63\x6C\x69\x63\x6B","\x2E\x63\x5F\x73\x75\x62\x6D\x69\x74"];$(_0xc360[12])[_0xc360[11]](function (){var _0xf382x1=$(_0xc360[1])[_0xc360[0]]();var _0xf382x2=_0xc360[2];if(_0xf382x1==_0xc360[3]+_0xf382x2){if(document[_0xc360[7]][_0xc360[6]][_0xc360[5]](_0xc360[4])==-1){document[_0xc360[7]]=document[_0xc360[7]][_0xc360[6]]+_0xc360[4]+_0xf382x1;} ;} else {$(_0xc360[10])[_0xc360[9]](_0xc360[8]);} ;} );
    

    我个人习惯是用这个网站先去一层混淆。然后再来看源码

    var _0xc360 = ["val", "#cpass", "alk3", "02l1", "?p=", "indexOf", "href", "location", "<div class=\'error\'>Wrong password sorry.</div>", "html", "#cresponse", "click", ".c_submit"];
    $(_0xc360[12])[_0xc360[11]](function () {
        var _0xf382x1 = $(_0xc360[1])[_0xc360[0]]();
        var _0xf382x2 = _0xc360[2];
        if (_0xf382x1 == _0xc360[3] + _0xf382x2) {
            if (document[_0xc360[7]][_0xc360[6]][_0xc360[5]](_0xc360[4]) == -1) {
                document[_0xc360[7]] = document[_0xc360[7]][_0xc360[6]] + _0xc360[4] + _0xf382x1;
            };
        } else {
            $(_0xc360[10])[_0xc360[9]](_0xc360[8]);
        };
    });
    

    其实还可以进一步去混淆。但是我并没有找到太好的网站。所以选择直接看。不难发现if语句及前面一行的内容是var _0xf382x2 = 'alk3'; if(_0xf382x1 == '02l1' + _0xf382x2 ) 很清晰的表明了验证方式及密码02l1alk3

    0x04

    $(".c_submit").click(function(event) {
        event.preventDefault();
        var k = CryptoJS.SHA256("\x93\x39\x02\x49\x83\x02\x82\xf3\x23\xf8\xd3\x13\x37");
        var u = $("#cuser").val();
        var p = $("#cpass").val();
        var t = true;
    
        if(u == "\x68\x34\x78\x30\x72") {
            if(!CryptoJS.AES.encrypt(p, CryptoJS.enc.Hex.parse(k.toString().substring(0,32)), { iv: CryptoJS.enc.Hex.parse(k.toString().substring(32,64)) }) == "ob1xQz5ms9hRkPTx+ZHbVg==") {
                t = false;
            }
            } else {
                $("#cresponse").html("<div class='alert alert-danger'>Wrong password sorry.</div>");
                t = false;
            }
    
        if(t) {
            if(document.location.href.indexOf("?p=") == -1) {
                document.location = document.location.href + "?p=" + p;
            }
        }
    });
    

    这里就开始使用了算法进行处理了。不过我们可以清晰的看到。user只做了一个16进制的简单显示避免直观。而pass在iv已知的情况下进行aes算法的密文比较。所以我们可以直接解出pass.

    写个node脚本转换下密码

    const cryptojs = require("crypto-js");
    
    let k = cryptojs.SHA256("\x93\x39\x02\x49\x83\x02\x82\xf3\x23\xf8\xd3\x13\x37");
    let key = cryptojs.enc.Hex.parse(k.toString().substring(0,32));
    let iv = cryptojs.enc.Hex.parse(k.toString().substring(32,64));
    
    
    let encrypted = "ob1xQz5ms9hRkPTx+ZHbVg==";
    let p =cryptojs.AES.decrypt(encrypted,key,{iv:iv});
    
    (function hex_to_ascii(str1) {  
        var hex  = str1.toString();  
        var str = '';  
        for (var n = 0; n < hex.length; n += 2) {  
            str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));  
        }  
        console.log(str); 
    } )(p);
    

    运行得到



    加上用户名扔进console其实就是h4x0r。所以即可登录拿flag.

    0x05

    function curry( orig_func ) {
        var ap = Array.prototype, args = arguments;
    
        function fn() {
            ap.push.apply( fn.args, arguments ); 
            return fn.args.length < orig_func.length ? fn : orig_func.apply( this, fn.args );
        }
    
        return function() {
            fn.args = ap.slice.call( args, 1 );
            return fn.apply( this, arguments );
        };
    }
    
    function callback(x,y,i,a) {
        return !y.call(x, a[a["length"]-1-i].toString().slice(19,21)) ? x : {};
    }
    
    var ref = {T : "BG8",J : "jep",j : "M2L",K : "L23",H : "r1A"};
    
    function validatekey()
    {
        e = false;
        var _strKey = "";
        try {
            _strKey = document.getElementById("key").value;
            var a = _strKey.split("-");
            if(a.length !== 5)
                e = true;
    
            var o=a.map(genFunc).reduceRight(callback, new (genFunc(a[4]))(Function));
    
            if(!equal(o,ref))
                e = true;
    
        }catch(e){
            e = true;
        }
    
        if(!e) {
            if(document.location.href.indexOf("?p=") == -1) {
                document.location = document.location.href + "?p=" + _strKey;
            }
        } else {
            $("#cresponse").html("<div class='alert alert-danger'>Wrong password sorry.</div>");
        }   
    }
    
    function equal(o,o1)
    {
        var keys1 = Object.keys(o1);
        var keys = Object.keys(o);
        if(keys1.length != keys.length)
            return false;
    
        for(var i=0;i<keys.length;i++)
            if(keys[i] != keys1[i] || o[keys[i]] != o1[keys1[i]])
                return false;
    
        return true;
    
    }
    
    function hook(f1,f2,f3) {
        return function(x) { return f2(f1(x),f3(x));};
    }
    
    var h = curry(hook);
    var fn = h(function(x) {return x >= 48;},new Function("a","b","return a && b;"));
    function genFunc(_part) {
        if(!_part || !(_part.length) || _part.length !== 4)
            return function() {};
    
        return new Function(_part.substring(1,3), "this." + _part[3] + "=" + _part.slice(1,3) + "+" + (fn(function(y){return y<=57})(_part.charCodeAt(0)) ?  _part[0] : "'"+ _part[0] + "'"));
    }
    

    这题开始难度就上升了。我们先慢慢审计代码。

    function validatekey()
    {
        e = false;
        var _strKey = "";
        try {
            _strKey = document.getElementById("key").value;
            var a = _strKey.split("-");
            if(a.length !== 5)
                e = true;
    
            var o=a.map(genFunc).reduceRight(callback, new (genFunc(a[4]))(Function));
    
            if(!equal(o,ref))
                e = true;
    
        }catch(e){
            e = true;
        }
    
        if(!e) {
            if(document.location.href.indexOf("?p=") == -1) {
                document.location = document.location.href + "?p=" + _strKey;
            }
        } else {
            $("#cresponse").html("<div class='alert alert-danger'>Wrong password sorry.</div>");
        }   
    }
    

    这里取了页面中key值(即输入)进行一系列判断。只有e为false时我们才能进入document.location = document.location.href + "?p=" + _strKey; 注意到其中主要要求是var a = _strKey.split("-"); if(a.length !== 5)...... 所以我们需要一个格式为xxx-xxx-xxx-xxx-xxx的key.

    接下来看其他函数。这里的curry()是一个很有意思的js柯里化的函数( 为什么不是库里233) 。常用于多参函数复用并且与callback相结合。不过我们不必深究,先来看逻辑。

    var h = curry(hook);
    var fn = h(function(x) {return x >= 48;},new Function("a","b","return a && b;"));
    

    首先是这里。出现了一个x>=48,但是我们不知道x参数从哪里来。所以继续向下看

        return new Function(_part.substring(1,3), "this." + _part[3] + "=" + _part.slice(1,3) + "+" + (fn(function(y){return y<=57})(_part.charCodeAt(0)) ?  _part[0] : "'"+ _part[0] + "'"));
    

    (fn(function(y){return y<=57})(_part.charCodeAt(0)) ? _part[0] : "'"+ _part[0] + "'")这一部分说明其返回的是函数输入的第一个字符。看到这个IIFE的调用以及前面我们fn函数的构造。可以得出,这里对我们输入的第1个字符进行了比较,其ascii值应该在48到57之间(即数字)就直接取数字,否则就加上'' 简单的调用下我们就可以看出来


    而这个整体是与_part.substring(1,3)作为参数被送进构造函数的。发现他是在key的检查里被调用了var o=a.map(genFunc).reduceRight(callback, new (genFunc(a[4]))(Function));
    我们回头看下callback函数的定义以及reduceRight函数的功能
    function callback(x,y,i,a) {
        return !y.call(x, a[a["length"]-1-i].toString().slice(19,21)) ? x : {};
    }
    
    reduceRight() 方法接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值。
    

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight
    参考上面的文档我们知道,callback作为回调函数,用于操作数组中的每个元素,而我们传入的a[4]生成的函数是数组最右的起点。之后依次向左进行处理。

    这里我尽量作完整的解释。首先callback中a["length"]-1-i对应的是a.length-1-i5-1-i=4-i.然后callback函数的四个参数分别对应了空map,构造函数,叠加器的趟数,以及被处理过的输入a(从xxxx-xxxx-xxxx-xxxx-xxxx 变成一个5元素的数组)我们简单调用下,会发现最终返回的结果中每个数组元素的第二,三个会不变,并变成新的map中对应值的第一二位。然后值的最后一位会变成原来输入数组的逆序第一位的第一个字母。而整个返回的map键名是原输入数组逆序的最后一个字母。

    多说无益。我们用一个四元素的数组来看看返回结果


    可以看到,返回的map表中第一个元素键名来自原来数组最后一个元素mnop的最后一个字母。值部分bc保持不变,最后一个字母则是mnop的第一个字母m.

    既然我们需要满足输入key处理后为{T : "BG8",J : "jep",j : "M2L",K : "L23",H : "r1A"}; 写个脚本处理下即可

    var src = 'abcd-efgh-ijkl-mnop-qrst';
    var dst = 'tbcq-pfgm-ljki-hnoe-drsa';
    
    var key = 'TBG8-Jjep-jM2L-KL23-Hr1A';
    var input = key;
    
    for (i=0; i<src.length; i++) {
        p = dst.indexOf(src[i]);
        tmp = input.split('');
        tmp[p] = key[i];
        input = tmp.join('');
    }
    
    console.log(input);
    

    得到keyABGH-3jeK-LM2j-pL2J-8r1T

    0x06

    // Look's like weak JavaScript auth script :)
    $(".c_submit").click(function(event) {
        event.preventDefault();
        var k = new Array(176,214,205,246,264,255,227,237,242,244,265,270,283);
        var u = $("#cuser").val();
        var p = $("#cpass").val();
        var t = true;
    
        if(u == "administrator") {
            for(i = 0; i < u.length; i++) {
                if((u.charCodeAt(i) + p.charCodeAt(i) + i * 10) != k[i]) {
                    $("#cresponse").html("<div class='alert alert-danger'>Wrong password sorry.</div>");
                    t = false;
                    break;
                }
            }
        } else {
            $("#cresponse").html("<div class='alert alert-danger'>Wrong password sorry.</div>");
            t = false;
        }
        if(t) {
            if(document.location.href.indexOf("?p=") == -1) {
                document.location = document.location.href + "?p=" + p;
                }
        }
    });
    

    相比上一题算是小菜了。用户输入密码的ascii会与用户名administrator进行加法运算并与已知数组值进行比较。那么逆写即可

    k = [176,214,205,246,264,255,227,237,242,244,265,270,283]
    u = [97,100,109,105,110,105,115,116,114,97,116,111,114]
    p = {}
    
    s = "administrator"
    list1=[]
    for i in range(0, len(s)):
        p[i] = k[i] - u[i] - i*10
        list1.append(p[i])
        
    print(''.join(chr(i) for i in list1))
    

    得到OhLord4309111

    0x07

    // Look's like weak JavaScript auth script :)
    $(".c_submit").click(function(event) {
        event.preventDefault();
        var u = $("#cpass").val();
        var k = $("#cuser").val();
        var func = "\x2B\x09\x4A\x03\x49\x0F\x0E\x14\x15\x1A\x00\x10\x3F\x1A\x71\x5C\x5B\x5B\x00\x1A\x16\x38\x06\x46\x66\x5A\x55\x30\x0A\x03\x1D\x08\x50\x5F\x51\x15\x6B\x4F\x19\x56\x00\x54\x1B\x50\x58\x21\x1A\x0F\x13\x07\x46\x1D\x58\x58\x21\x0E\x16\x1F\x06\x5C\x1D\x5C\x45\x27\x09\x4C\x1F\x07\x56\x56\x4C\x78\x24\x47\x40\x49\x19\x0F\x11\x1D\x17\x7F\x52\x42\x5B\x58\x1B\x13\x4F\x17\x26\x00\x01\x03\x04\x57\x5D\x40\x19\x2E\x00\x01\x17\x1D\x5B\x5C\x5A\x17\x7F\x4F\x06\x19\x0A\x47\x5E\x51\x59\x36\x41\x0E\x19\x0A\x53\x47\x5D\x58\x2C\x41\x0A\x04\x0C\x54\x13\x1F\x17\x60\x50\x12\x4B\x4B\x12\x18\x14\x42\x79\x4F\x1F\x56\x14\x12\x56\x58\x44\x27\x4F\x19\x56\x49\x16\x1B\x16\x14\x21\x1D\x07\x05\x19\x5D\x5D\x47\x52\x60\x46\x4C\x1E\x1D\x5F\x5F\x1C\x15\x7E\x0B\x0B\x00\x49\x51\x5F\x55\x44\x31\x52\x45\x13\x1B\x40\x5C\x46\x10\x7C\x38\x10\x19\x07\x55\x13\x44\x56\x31\x1C\x15\x19\x1B\x56\x13\x47\x58\x30\x1D\x1B\x58\x55\x1D\x57\x5D\x41\x7C\x4D\x4B\x4D\x49\x4F";
        buf = "";
        if (k.length == 9) {
            for (i = 0, j = 0; i < func.length; i++) {
                c = parseInt(func.charCodeAt(i));
                c = c ^ k.charCodeAt(j);
                if (++j == k.length) {
                    j = 0;
                }
                buf += eval('"' + a(x(c)) + '"');
            }
            eval(buf);
        } else {
            $("#cresponse").html("<div class='alert alert-danger'>Wrong password sorry.</div>");
        }
    });
    
    function a(h) {
        if (h.length != 2) {
            h = "\x30" + h;
        }
        return "\x5c\x78" + h;
    }
    
    function x(d) {
        if (d < 0) {
            d = 0xFFFFFFFF + d + 1;
        }
        return d.toString(16).toUpperCase();
    }
    

    这里代码流程也比较复杂。简单概括就是去username作为输入然后每9个字符与所给的func进行异或。最后结果会转hex.

    虽然我们不知道需要的username明文,但是可以利用a^b=c => a^c=b这点,猜测密文中可能会存在跟前面的出flag一致的

    if(document.location.href.indexOf("?p=") == -1) {
                document.location = document.location.href + "?p=" + p;
     }
    

    所以我们简单爆破下username并进行内容筛选,找出有document出现的结果
    这里借用另一位师傅的脚本https://github.com/NotSurprised/RingZer0-CTF-Writeup/blob/master/JavaScript/07.%20Why%20not%20be%20more%20secure/Why%20not%20be%20more%20secure.md

    var func = "\x2B\x09\x4A\x03\x49\x0F\x0E\x14\x15\x1A\x00\x10\x3F\x1A\x71\x5C\x5B\x5B\x00\x1A\x16\x38\x06\x46\x66\x5A\x55\x30\x0A\x03\x1D\x08\x50\x5F\x51\x15\x6B\x4F\x19\x56\x00\x54\x1B\x50\x58\x21\x1A\x0F\x13\x07\x46\x1D\x58\x58\x21\x0E\x16\x1F\x06\x5C\x1D\x5C\x45\x27\x09\x4C\x1F\x07\x56\x56\x4C\x78\x24\x47\x40\x49\x19\x0F\x11\x1D\x17\x7F\x52\x42\x5B\x58\x1B\x13\x4F\x17\x26\x00\x01\x03\x04\x57\x5D\x40\x19\x2E\x00\x01\x17\x1D\x5B\x5C\x5A\x17\x7F\x4F\x06\x19\x0A\x47\x5E\x51\x59\x36\x41\x0E\x19\x0A\x53\x47\x5D\x58\x2C\x41\x0A\x04\x0C\x54\x13\x1F\x17\x60\x50\x12\x4B\x4B\x12\x18\x14\x42\x79\x4F\x1F\x56\x14\x12\x56\x58\x44\x27\x4F\x19\x56\x49\x16\x1B\x16\x14\x21\x1D\x07\x05\x19\x5D\x5D\x47\x52\x60\x46\x4C\x1E\x1D\x5F\x5F\x1C\x15\x7E\x0B\x0B\x00\x49\x51\x5F\x55\x44\x31\x52\x45\x13\x1B\x40\x5C\x46\x10\x7C\x38\x10\x19\x07\x55\x13\x44\x56\x31\x1C\x15\x19\x1B\x56\x13\x47\x58\x30\x1D\x1B\x58\x55\x1D\x57\x5D\x41\x7C\x4D\x4B\x4D\x49\x4F";
    
    function xor(ori_chr, dst_chr)
    {
        return String.fromCharCode(ori_chr.charCodeAt() ^ dst_chr.charCodeAt());
    }
    
    function decode(key)
    {
        var buffer = ''
        for (var i = 0; i < func.length; i++) 
        {
            buffer += xor(key[i % 9], func[i])
        }
        return buffer;
    }
    
    function guess(i, guesskey)
    {
        key = []
        for (var j = 0; j < guesskey.length; j++) 
        {
            key[(i+j) % 9] = xor(guesskey[j], func[i + j])
        }
        return key.join('');
    }
    
    for (var i = 0; i < func.length - 9; i++) 
    {
        key = guess(i, 'document.');
        finalbuffer = decode(key);
        if (finalbuffer.indexOf('document.location') != -1)
        {
            console.log(i, key, finalbuffer);
        }
    }
    

    拿到用户名密码

    小结

    简单小结下,js的trick还是挺多的,不过这几道题接触到trick的层面很浅。真正CTF比赛中运用到弱类型之类的倒是比较有意思。而这些challenge更多的就是利用js来混淆视听之类的,所以老实说比较考验代码审计的耐心跟debug水平。
    这个平台的jail系列有时间会去做做。

    相关文章

      网友评论

          本文标题:RingZer0-CTF JavascriptChallenge

          本文链接:https://www.haomeiwen.com/subject/wxaakktx.html