„Ajťák“ Joker, osobní stránky

Nacházíte se v části webu: Hlavní stránka › Počítače, weby

Zabezpečení hesla z pohledu programátora

napsal Joker, 21.2.2009 19.49:14, naposledy upraveno 21.2.2009 20.29:04

Kdysi jsem tu napsal článek o zabezpečení hesla z pohledu uživatele. Volba silného hesla na straně uživatele je samozřejmě jen jedna část, to hlavní je na programátorovi. Tak nějak jsem předpokládal, že programuje-li někdo nějakou serioznější webovou aplikaci, alespoň nějaký pokus o zabezpečení vždy udělá. Byl jsem vyveden z omylu.

Protože jsem nenašel článek shrnující nějaké základy zabezpečení hesel pro programátory (když už nějaký je, obvykle rozebírá jednu konkrétní metodu), zkusím takový souhrn napsat sám.

Zaměřím se na hesla na webových stránkách. Nepůjde o nějaké objevné informace, spíš shrnutí základů. Předpokládejme, že máme webovou aplikaci, heslo se ukládá v nějakém úložišti (typicky databáze) odděleně od samotného kódu aplikace.

Na předchozí stránce jsme načrtli možné směry, ze kterých lze útok očekávat, nyní se podívejme, jak se proti útoku chránit.

Ochrana proti prolomení hesla

V této části se podíváme, jak je možné se chránit proti útoku spočívajícím v získání záznamu o uživateli z databáze a následně pokusu o vytěžení uživatelova hesla z tohoto záznamu.

Úroveň 0: hashování hesla

Píšu úroveň 0, protože toto je dle mého názoru naprostý základ- v databázi by nikdy nemělo být uložené heslo v přímo čitelném formátu! Krom potenciálních útočníků existují lidé, kteří mají přístup k databázi i "legálně"- například správce/-i webu. A není vhodné, aby dokonce ani správce webu mohl mít přehled o heslech uživatelů. Případně aby hesla třeba mimoděk získal vývojář při řešení nějaké chyby. Tady jak se říká příležitost dělá zloděje a hodně lidí když už náhodou zjistí něčí heslo, řekne si: proč se jen tak nepodívat...

Takže, pokud to není součást nějakého opravdu "ženiálního plánu" na zabezpečení aplikace, je uložení hesla v databázi v čitelné podobě základní bezpečnostní chyba.

Proto se k ukládání hesla do databáze používají hashovací funkce. Výsledkem hashovací funkce je hash, neboli otisk původního hesla. Hashovací funkce má tu vlastnost, že stejné heslo vždy produkuje stejný hash (díky tomu ověříme, že heslo je stejné). Zároveň nelze vysledovat souvislost mezi změnou hesla a změnou hashe (tzn. například na základě hashů různých hesel nelze říct, jestli hesla tvoří podobná slova, nebo úplně jiná). Důležité je, že hash není šifrování a z hashe už nikdy nezískáte zpět vstup, který hash vytvořil. To je dané tím, že zahashovat lze jakákoliv data- heslo, obrázek, nebo třeba film na DVD... přitom hash má vždy pevnou délku, například MD5 má 32 bajtů. Z toho je jasné, že zatímco hashů je nějaký omezený počet (byť jich může být opravdu hodně), možných vstupů je nekonečně mnoho. Logicky tedy musejí existovat různé vstupy dávající tentýž hash. Tomu se říká kolize. Tedy, můžete sice najít kolizi, ale může existovat nekonečně mnoho jiných kolizí a ze samotného hashe nikdy nezjistíte, která z nich byla vstupem, který hash vytvořil.

Ověření uživatele se pak dělá: Pokud hash zadaného hesla je stejný jako hash uložený v databázi, uživatel je ověřen. Z předchozího odstavce tedy vyplývá, že kvůli existenci kolizí je teoreticky možné ověřit uživatele na základě jiného řetězce, než správného hesla. Teoreticky to možné je, nicméně u běžného hesla je daleko pravděpodobnější se trefit do správného hesla, než do špatného hesla se stejným hashem jako to správné.

Úroveň zabezpečení: základní.

Jednoduché hashování hesla sice poskytuje určitou míru zabezpečení, zejména útočník už nedostane heslo přímo naservírované, ale musí vyvinout aktivitu k jeho získání. Útočník má zhruba tři možnosti, jak heslo získat:

  1. Hrubou silou: útočník si hash zkopíruje a následně si někde stranou generuje hashe různých řetězců tak dlouho, až najde kolizi. Obvykle se spíše než čistě porovnávání náhodných řetězců použije slovníkový útok, kdy se porovnávají hashe proti slovům ze slovníku a útočník doufá, že oběť zvolila jako heslo některé z těch slov. Pokud si ale oběť zvolí silné heslo, je tato metoda prakticky k ničemu.
  2. Tzv. rainbow tables: velké množství počítačů hashuje různé řetězce a z výsledků tvoří databázi řetězec-hash. Útočník se pokusí vyhledat hash v databázi. Toto je obvykle první věc, kterou útočník zkusí. Pro silná hesla je ovšem pravděpodobnost úspěchu opět malá.
  3. Prolomit samotnou hashovací funkci. Předpokladem je bezpečnostní chyba v samotné hashovací funkci. Toto je případ třeba funkce MD5, kde je dnes i na běžném počítači možné najít kolizní řetězec vpodstatě okamžitě.

Je třeba si uvědomit, že i když prolomit dobrou funkcí zahashované silné heslo může být obtížné, útočníkovi často stačí prolomit alespoň některé z uživatelských účtů. Pokud se dostane k seznamu hashů, je velmi snadné ho porovnat proti rainbow tables a rychle tak prolomit účty uživatelů, kteří si zvolili slabá hesla.

Kdysi jsem dělal takový průzkum: na diskusním fóru s cca 750 registrovanými uživateli, kde hesla byla ukládána jako prostý hash, jsem nechal seřadit hashe podle počtu výskytů v tabulce (pozn.: výpis byl jen seznam hashů, žádné další údaje ani spárování s konkrétními uživateli). Nejčastější heslo mělo 13 výskytů, druhé nejčastější 8 nebo 9. Obě nejčastější hesla jsem byl schopný uhodnout z hlavy (ověřeno vygenerováním hashe pro domnělé heslo a porovnáním) a celkem jsem byl jen na základě několika z hlavy tipnutých hesel schopný prolomit zhruba 20-30 uživatelských účtů. S použitím nějaké dokonalejší metody (třeba rainbow tables) by to mohlo být nejspíš podstatně více.

Úroveň 1: solený hash

Jde o vylepšení jednoduchého hashe. Při ukládání hesla server vezme ještě řetězec, kterému se říká sůl. Tento řetězec server určitým způsobem zkombinuje s heslem a teprve z výsledku udělá hash a ten uloží do databáze. Krom hashe je samozřejmě uložená i sůl. Ověřování uživatele pak probíhá tak, že server vezme vstup od uživatele, zkombinuje ho se solí, z výsledku udělá hash a porovná s databází. Sůl je možné uložit jednotně v kódu aplikace, nebo v databázi pro každého uživatele zvlášť. Uložení v databázi pro každého uživatele má nevýhodu, že při získání výpisu z databáze útočník získá jak hash, tak i sůl. Na druhou stranu je ale výhoda, že i když budou mít uživatelé stejná hesla, budou mít jinou sůl, takže výsledný hash bude jiný. Díky tomu nelze poznat, kteří uživatelé mají stejná hesla.

Jedna sůl pro všechny uživatele teoreticky umožňuje útočníkovi, který může získat výpis z databáze, následující útok: Na webu si založí několik uživatelských účtů a nastaví jim hesla, o kterých předpokládá, že je bude mít co nejvíce ostatních uživatelů. Poté získá výpis, ze kterého je schopen získat uživatele se stejným heslem, jako některý s jím založených účtů.

Každopádně ale solený hash oproti prostému hashování znamená velký nárůst bezpečnosti:

  • Sůl generovaná z náhodných znaků zesiluje zadané heslo a výrazně ztěžuje slovníkový útok a útok přes rainbow tables.
  • Útočník místo jedné informace (hash) potřebuje znát tři nezávislé informace: hash, sůl a algoritmus, jakým se sůl kombinuje se vstupním heslem. Přitom hash je v databázi, algoritmus v kódu aplikace a sůl může být v databázi nebo v kódu.
  • I při prolomení hashovací funkce útočníkovi nestačí najít libovolnou kolizi, musí najít kolizi obsahující sůl, pak z ní tu sůl odstranit a výsledek zadat jako heslo. To je mnohem složitější úkol, než jen získat libovolnou kolizi

Úroveň zabezpečení: dobrá. Dle mého názoru jde o pro běžné projekty dostačující ochranu, proti prolomení hesla na základě získání dat ze serveru. Samozřejmě předpokládám, že programátoři například bankovních systémů nemají zapotřebí číst tento článek.

Ani jedna z uvedených metod ale nechrání proti odposlechu hesla "po cestě", na což se podíváme nyní.

Ochrana proti odposlechnutí hesla

Proti odposlechnutí hesla se lze bránit použitím šifrovaného protokolu https. Ne vždy ale lze tento protokol vzhledem k podmínkám použít. Při použití nešifrovaného protokolu http je obrana složitější, ale přesto možná.

Ověření challenge-response (výzva-odpověď)

Aby se zabránilo odposlechu hesla, logicky se nesmí posílat přímo samotné heslo. Proto se používá metoda challenge-response. Bližší popis najdete třeba v článku na root.cz, tady to zkusím stručněji. Challenge-response spočívá v tom, že server vytvoří řetězec- "výzvu" a pošle ji klientovi (počítači uživatele). Uživatel zadá heslo, ještě v jeho počítači se heslo určitým způsobem zkombinuje s výzvou, z této kombinace se vytvoří hash a ten se pošle serveru. Server pak vezme výzvu, kterou uživateli přidělil, zkombinuje uživatelovo heslo (to má v databázi) s výzvou, udělá hash a ověří, zda je shodný jako hash, který poslal uživatel. V tomto případě se po síti neposílá heslo, takže ho není možné ani odposlechnout. Je možné získat jen otisk kombinace hesla s výzvou, případně ještě samotnou výzvu. Prolomení je zhruba tak složité, jako solený hash.

Problém spočívá v tom, že k použití této metody musíme mít v databázi uložené "otevřené" heslo (respektive v databázi může být uložený i hash toho, co se zadává do okénka "heslo", ale při ověřování challenge-response používáme ten hash jako otevřené heslo- útočníkovi stačí získat hash hesla z databáze a přes challenge-response se dokáže přihlásit). Kdybychom měli jen hash hesla, nebudeme schopni na serveru zkombinovat heslo s výzvou. Tím se metoda naopak stává nebezpečnou z hlediska možného získání dat z databáze. V uvedeném článku je řešení:

Pro daného uživatele si server ukládá i výzvu, která byla použita při posledním úspěšném přihlášení a hash minulé odpovědi od uživatele (tedy vlastně hash hashe kombinace hesla a minulé výzvy). Při novém přihlášení pak server pošle uživateli novou výzvu a po zadání uživatelského jména (AJAX?) mu pošle ještě minulou výzvu pro daného uživatele. Skript na počítači uživatele pak zkombinuje heslo se starou výzvou, z toho udělá hash (řekněme "stará odpověď") a pošle serveru. Krom toho zkombinuje heslo s novou výzvou, z toho udělá hash, ten ještě jednou zahashuje, takže vznikne "hash nové odpovědi". Ten pošle také serveru. Server převezme z klienta starou odpověď, udělá její hash a porovná s databází. Pokud je správný, uživatele přihlásí a v databázi přenastaví minulou výzvu a hash minulé odpovědi.

Princip je v tom, že se sice používá otevřené heslo, ale server ho nepotřebuje znát- jen při prvním nastavení hesla si zároveň uloží výzvu a hash správné odpovědi. Každou další odpověď pak vždy ověřuje podle hashe předchozí odpovědi, který pak při úspěšném přihlášení vždy zahodí a uloží si místo něj hash aktuální odpovědi. Útočník tedy může odposlechnout jen starou odpověď, která -pokud byla správná- mu k ničemu není, protože server ji po přihlášení zahodí. Také získá hash nové odpovědi, který mu ale taky k ničemu není, protože při dalším přihlášení po něm server bude chtít tu odpověď, ne hash.

V případě, kdy uživatel např. nemá zapnutý Javascript, a tedy nemůže sestavit odpověď, může stále odeslat samotné heslo a server může sestavit a zkontrolovat odpověď sám. Samozřejmě to ale pak nebude zabezpečené proti odposlechu.

Kritické místo tohoto je, že útočník jak v případě odposlechu, tak i získání dat z databáze získá jednoduchý hash toho, co musí poslat pro úspěšné přihlášení. Bylo by proto vhodné použít dostatečně odolnou hashovací funkci. Popřípadě by i toto šlo ještě zodolnit soleným hashem: uživatel by na server místo jednoduchého hashe poslal solený hash a současně sůl. Při dalším přihlášení by server převzal odpověď, přidal sůl a vytvořil solený hash, který by pak kontroloval.

Další pozitivum z hlediska bezpečnosti je, že každý hash je platný jen do příštího přihlášení, což hodně omezuje možnost zneužití.

Úroveň zabezpečení: dobrá. Připomínám ale, že tato metoda je odolná jen proti odposlechu dat a ne proti útoku "Man in the middle", pokud útočník dokáže přenášená data i modifikovat. V takovém případě může útočník oběti do formuláře podstrčit například Javascript, který mu pošle i samotné heslo. Proti takovémuto útoku pomůže leda šifrovaná komunikace, kdy se obě strany navzájem ověřují certifikáty.

Závěr

Do databáze se zásadně neukládá heslo v čitelném formátu.

Ukládání prostého hashe do databáze poskytne určitou minimální úroveň zabezpečení. Lepší úrovně zabezpečení se ale dosáhne použitím soleného hashe anebo metody challenge-response. Solený hash mi přijde jako dostačující metoda, jelikož získání informací z databáze je asi častější, než odposlouchávání připojení.

Pro znemožnění útoku hrubou silou je také vhodné donutit uživatele volit dostatečně silná hesla. Na druhé straně je potřeba dbát i na komfort uživatelů a nenutit je zadávat např. 50-znaková hesla.

Přejít na stránku: [ 1 2 ]

[1] od: bohyn × (25.2.2009 17.40:58)
Ad soleni hashe: Sul se da kombinovat dynamicka a staticka treba "uz_jmeno@heslo" a ziskat tak vyhodu obojiho.

[2] od: df × (26.10.2009 13.23:55)
Musim vas zklamat. Https neni zadnou obranou proti MITM. Viz http://cs.wikipedia.org/wiki/Man_in_the_middle

[3] od: Joker ® (26.10.2009 16.51:46)
[2] Proč ne? Při zahájení HTTPS komunikace mám veřejný klíč protistrany, nejlépe ověřený nezávislým komunikačním kanálem. Útočník tedy bez znalosti soukromého klíče nedokáže předstírat druhou stranu komunikace, ani za běhu modifikovat data. Při použití dostatečně silného šifrování ideálně nedokáže ani v použitelném čase dešifrovat obsah komunikace.

[4] od: M2D HASH × (11.5.2010 5.26:16)
kurvaa

[5] od: Seith × (5.3.2011 12.39:50)
Ahoj, mám dotaz ohledně nutnosti použít AJAX a posílat minulou výzvu až po zadání hesla. Nevidím bezpečnostní rozdíl v tom, když dojde k odeslání minulé výzvy společně s tou "aktuální" při zobrazení formuláře. Je v tom nějaký zásadní rozdíl?

[6] od: </body> × (13.3.2011 15.51:27)
heh

Přidat nový komentář