Pokud bezpečně ukládáme hesla, tak je uživatelům samozřejmě nemůžeme poslat v případě zapomenutí, protože je nemáme jak získat. Nabízí se ale několik způsobů, jak zapomenutí hesel řešit i v případě jejich bezpečného uložení.
Nevhodný způsob spočívá v tom, že uživateli vygenerujeme nové heslo a pošleme mu ho. To totiž umožňuje útočníkovi opakovaně tvrdit, že zapomněl heslo nějakého uživatele, a tím mu znemožnit přihlášení (nebo alespoň znepříjemnit život tím, že přestane fungovat jeho původní heslo). Jde vlastně o útok DoS.
Lepší je proto vygenerovat uživateli token (náhodný řetězec), pomocí kterého si bude moci heslo po omezenou dobu změnit bez toho, aniž by heslo znal. Tento token mu následně pošleme. Ve zprávě je vhodné zohlednit i situaci, kdy uživatel o změnu zapomenutého hesla nežádal nebo si na něj mezitím vzpomněl (což zachová původní heslo a smaže token).
Při ukládání tokenu na straně serveru se často chybuje, protože se do databáze zapisuje v podobě, která se posílá uživateli. Tím pádem jde o klasické schéma Security by Obscurity – v případě kompromitace úložiště by útočník mohl změnit heslo kterémukoliv uživateli (pro kterého by si pomocí aplikace nejprve vygeneroval token). Když člověk přemýšlí o bezpečnosti aplikace, tak je vhodné za „útočníka“ dosadit programátora aplikace, který má přístup ke všem zdrojovým kódům a kopii databáze (vytvářené třeba pro účely vývojového prostředí). To je nutným předpokladem pro schéma Security by Design. Problém vyřešíme tím, že místo tokenu uložíme jeho haš.
<?php // zapomenutí hesla $token = md5(uniqid(rand(), true)); mysql_query(" UPDATE uzivatele SET heslo_token = '" . md5($token) . "', heslo_token_platnost = NOW() + INTERVAL 1 DAY WHERE login = '" . mysql_real_escape_string($_POST["login"]) . "' "); mail($email, "Zapomenute heslo", "http://$_SERVER[SERVER_NAME]/obnoveni-hesla.php?login=" . urlencode($_POST["login"]) . "&token=$token"); // na stránce obnoveni-hesla.php zobrazíme formulář pro zadání nového hesla nebo zrušení žádosti // zrušení žádosti o zapomenuté heslo mysql_query(" UPDATE uzivatele SET heslo_token = NULL WHERE login = '" . mysql_real_escape_string($_GET["login"]) . "' AND heslo_token = '" . md5($_GET["token"]) . "' "); // změna zapomenutého hesla mysql_query(" UPDATE uzivatele SET heslo_sha1 = SHA1(CONCAT('" . mysql_real_escape_string($_POST["heslo"]) . "', heslo_salt)), heslo_token = NULL WHERE login = '" . mysql_real_escape_string($_GET["login"]) . "' AND heslo_token = '" . md5($_GET["token"]) . "' AND heslo_token_platnost >= NOW() "); ?>
Návod na změnu zapomenutého hesla musíme uživateli samozřejmě posílat na adresu uvedenou v době registrace, nikoliv až při zapomenutí hesla. Také je dobré vzít v potaz situaci, kdy uživatel kromě hesla zapomene i své přihlašovací jméno (a dovolit mu tedy zadat adresu, kterou uvedl při registraci). Poslání návodu pro změnu zapomenutého hesla se nejčastěji realizuje e-mailem, ten by ideálně měl být zašifrovaný.
Přijďte si o tomto tématu popovídat na školení Bezpečnost PHP aplikací.