CSRF útok s využitím CSS history hacku na získanie CSRF tokenov - Nethemba

BLOG

CSRF útok s využitím CSS history hacku na získanie CSRF tokenov

2011-01-23 19:49 Pavol Lupták

Úvod do CSRF

Unikátny (náhodný) číselný alebo alfanumerický token predstavuje bežnú ochranu súčasných aplikácií voči CSRF útokom. Citlivé POST formuláre zvyknú byť chránené unikátnym tokenom, ktorý je obvykle posielaný aplikáciou v “hidden field”, teda skrytom poli formulára. Citlivé GET žiadosti zase používajú v URL ďalší parameter – napríklad “csrftoken”. V oboch prípadoch sa aplikácia chráni pred nelegitímnymi žiadosťami, ktoré neprichádzajú od koncového používateľa, ale boli mu nejakým spôsobom vnútené (napríklad cez XSS zraniteľnosti, sociálne inžinierstvo atď). Pri CSRF útokoch útočník obvykle (pokým nemá kompletnú kontrolu nad koncovým používateľom) nedokáže odhadnúť akékoľvek jedinečné (náhodné) hodnoty GET/POST žiadostí, ktoré aplikácia spätne overuje (a na základe toho sa rozhodne, či danú žiadosť pokladá za legitímnu alebo nie), podobne nedokáže jednoducho získať odpoveď na svoju podvrhnutú GET/POST žiadosť a teda ani zistiť, či sa daná žiadosť úspešne vykonala alebo nie (dokáže to ale zistiť nepriamo, ak napríklad daná podvrhnutá POST žiadosť používateľovi zmenila heslo).

Hneď na začiatok je dôležité zdôrazniť, že akékoľvek citlivé údaje by sa nikdy nemali zasielať v GET žiadostiach (viď odporúčanie z testovacej príručky OWASP – Testing for Exposed Session Variables) a to preto, lebo obsah GET žiadostí (a teda citlivých informácii ako napríklad “session ID” alebo “CSRF token”) je štandardne cacheovaný vo všetkých prehliadačoch, ukladaný častokrát v proxy a firewall logoch a v neposlednom rade aj v logoch samotného webového servera. Preto je najlepšie na citlivé operácie používať výhradne POST žiadosti.

Veľa vývojárov riziko CSRF útokov vážne podceňuje a ako ochranu často používajú síce náhodne, ale len troj, štvor, v lepšom prípade päťciferné číselne tokeny. Však vyskúšať zaslať 100000, či nebodaj milión GET/POST žiadostí je náročné ako na Internetovú linku, útočníkové zdroje, tak na čas, ktorý musí byť dostatočne krátky na to, aby útočník zastihol prihláseného používateľa (ktorý sa v relatívne krátkom čase môže odhlásiť a tým pádom znemožní CSRF útok). Útočník totiž musí postupne zasielať všetky GET/POST s každým možným CSRF tokenom, aby mal istotu, že sa aspoň jeden (ten správny) určite vykoná. Rozumná aplikácia môže ale detekovať žiadosti s neplatným CSRF tokenom a útočníka hneď na začiatku zablokovať.

Ukážeme si veľmi zaujímavú a elegantnú “offline” techniku, kedy útočník nemusí svojej obeti vnútiť vykonanie milióna GET/POST žiadostí, ktoré trvajú extrémne dlho – tým pádom sa môže jednoducho vyhnúť zasielaniu neplatných CSRF tokenov a zablokovaniu zo strany aplikácie. Súčasne popíšeme viaceré spôsoby ako je možné vnucovať vlastné GET/POST žiadosti, ktoré sa vykonajú v kontexte samotného legitímneho používateľa (prehliadač použije na vykonanie podvrhnutých žiadosti svoje už existujúce “cookies”).

Prelomenie CSRF tokenov použitím CSS history hacku

Na to, aby uvedená technika (použitie CSS history hacku) fungovala, musia byť splnené nasledujúce podmienky:

1. Aplikácia musí daný CSRF token poslať minimálne raz v GET žiadosti, aby sa zacachoval do prehliadača (toto sa dá jednoducho odhaliť ľubovoľným “fault injection” proxy nástrojom, prípadne pluginom do Firefoxu akoLiveHTTPHeaders alebo TamperData). Veľa aplikácií uvedenú GET žiadosť zasiela rovno po tom ako sa používateľ úspešne prihlási do aplikácie.

2. Koncový prehliadač používateľa musí byť zraniteľný na CSS history hack (v súčasnej dobe je uvedená vlastnosť opravená len vo Firefox 4.x, všetky ostatné verzie Firefoxu sú zraniteľné na tento druh útoku). Súčasne nesmie používať špeciálne nástroje, ktoré znemožňujú ukladanie histórie (ako SafeHistory plugin).

Prvá vzorová implementácia tohto útoku bola popísana tu – Hacking CSRF Tokens usng CSS History Hack.

CSS history hack predstavuje spôsob ako vo všetkých súčasných prehliadačoch odhaliť postupným enumerovaním, ktoré presne URL (GET žiadosti) boli v minulosti daným prehliadačom navštívené – URL stránka, ktorú ste v minulosti už navštívili má inú farbu ako stránka, ktorá navštívená ešte nebola (t.j. má nastavený “a:visited” štýl v danom URL). CSS history hack prvýkrat popísal Jeremiah Grossman.

Samotný “offline” útok enumerovaním všetkých možných CSRF tokenov v prehliadači je v porovnaní s “online” vytváraním žiadostí veľmi  rýchly – prehľadanie 10000 kombinácií (teda možnosť odhaliť akýkoľvek 4-číselný CSRF token) na mojom niekoľkoročnom PC trvalo 3.5 sekundy (!), prehľadanie 100 000 kombinácií 40 sekúnd, prehľadanie milióna (!) kombinácií CSRF tokenov zhruba 6 minút.

Je nutné poznamenať, že ak používateľ behom poslednej doby bol v aplikácií prihlásený viackrát, tak budú odhalené všetky CSRF tokeny, ktoré behom tohto času boli použité (vrátane toho aktuálneho).

CSRF útok s odhalenými tokenmi
Samotný CSRF útok je možné realizovať viacerými spôsobmi:

1. Využitím HTML elementov – <img>, <iframe> apod, na vnútenie vykonania ľubovoľnej GET žiadostí, prípadne použitím “submitu” klasických formulárov <form> na vykonanie ľubovoľnej POST žiadosti.

2. Použitím AJAXu, kedy ľubovoľný GET a POST je možné vykonať pomocou jednoduchej  funkcie:

function postAJAX(url, query, handler)
{
    var status = false;
    var contentType = "application/x-www-form-urlencoded; charset=UTF-8";
    // Native XMLHttpRequest object
    if (window.XMLHttpRequest) {
        request = new XMLHttpRequest();
        request.onreadystatechange = handler;
        request.open("POST", url, true);
      //  request.setRequestHeader("Content-Type", contentType);
        request.setRequestHeader("Content-length", query.length);
        request.setRequestHeader("Connection","keep-alive");
        request.setRequestHeader("Referer",url);
        request.send(query);
        status = true;
    // ActiveX XMLHttpRequest object
    } else if (window.ActiveXObject) {
        request = new ActiveXObject("Microsoft.XMLHTTP");
        if (request) {
            request.onreadystatechange = handler;
            request.open("POST", url, true);
      //     request.setRequestHeader("Content-Type", contentType);
            request.setRequestHeader("Content-length", query.length);
            request.setRequestHeader("Connection","keep-alive");
            request.setRequestHeader("Referer",url);
            request.send(query);
            status = true;
        }
}
    return status;
}
V prípade vytvorenia POST žiadosti cez AJAX, som si všimol dva problémy, ktoré zrejme súvisia s bezpečnosťou FF (na testovanie som používal Firefox 3.6.13):
1. Ak daná POST žiadosť mala zadefinovaný ľubovoľný “Content-Type”, tak prehliadač danú žiadosť identifikoval ako HTTP metódu OPTIONS a tým pádom POST parametre boli vynechané. Po zakomentovaní nastavovania “Content-Type” sa tento problém vyriešil.
2. Môj prehliadač aj napriek tomu, že daná POST žiadosť bola v SOP kontexte aplikácie, ktorá bola paralelne otvorená v inom okne prehliadača, jej nepreposielal príslušne už existujúce “cookies”, čo je samozrejme nevyhnutné pri CSRF útokoch (zrejme ide o bezpečnostné opatrenie na strane prehliadača).
Toto bol dôvod, prečo som upustil od posielania POST žiadostí cez AJAX a použil som klasické HTML formuláre. Využil som javascript knižnicu jquery 1.4.4 a realizoval zasielanie POST žiadostí čisto cez HTML formulár:
function postToUrl(url, params, newWindow)
{
    var form = $('<form>');
    form.attr('action', url);
    form.attr('method', 'POST');
    if(newWindow){ form.attr('target', '_blank'); }
    var addParam = function(paramName, paramValue){
        var input = $('<input type="hidden">');
        input.attr({ 'id':     paramName,
                     'name':   paramName,
                     'value':  paramValue });
        form.append(input);
    };
    // Params is an Array.
    if(params instanceof Array){
        for(var i=0; i<params.length; i++){
            addParam(i, params[i]);
        }
    }
    // Params is an Associative array or Object.
    if(params instanceof Object){
        for(var key in params){
            addParam(key, params[key]);
        }
    }
    // Submit the form, then remove it from the page
    form.appendTo(document.body);
    form.submit();
    form.remove();
}
Môj funkčný “proof-of-concept exploit”, ktorý pre každý odhalený CSRF token z histórie prehliadača volá funkciu postToUrl() je možné stiahnuť tu.
Aktualizácia 28.1.2011 – Marek Laboš vytvoril optimalizovanu verziu uvedeného exploitu, ktorá je rádovo o polovicu rýchlejšia – k dispozícii na stiahnutie tu.

 

Čo s tým?

1. Na strane aplikácie je nevyhnutné používať dostatočné silné a unikátne CSRF tokeny  nespoliehať sa na ľahko uhádnuteľné a vyskúšateľné číselné kombinácie.

2. Pre každý formulár generovať unikátny CSRF token (nepoužívať jeden CSRF token pre celé spojenie).

3. Nikdy neposielať CSRF token (alebo “session ID”) v GET žiadostiach, ale výhradne len v POST formulároch.

4. V prípade, že aplikácia obdrží od používateľa invalidný token, tak je potrebné používateľa okamžite odhlásiť a vygenerovať bezpečnostný incident.

5. Na strane klienta použiť vhodnú ochranu voči CSS history hack – “private browsing“, “safehistory plugin” alebo Firefox 4.0.