[PHP] bezpečné přihlašování do admina?

C++, C#, Visual Basic, Delphi, Perl a ostatní

Moderátor: Moderátoři Živě.cz

Odeslat příspěvekod LastHunter 11. 7. 2005 14:06

Dobrý den,
protože s PHP ještě nemám moc zkušeností, obracím se na uživatele tohoto fóra s žádostí o pomoc...

Naprogramoval jsem si "administrační centrum" (http://www.nemesis-rs.wz.cz/anketa/admin) pro mou anketu (http://www.nemesis-rs.wz.cz/anketa/) a pokusil jsem se pro to administrační centrum vytvořit bezpečné přihlášení pomocí sessions (aby se tam nedostal někdo neoprávněný, ale skutečně pouze a jen člověk, který zná správné jméno a heslo).

Nejdřív můj postup popíši slovy a poté sem zkopíruji i části zdrojových kódů, které to mají na starost...:

V databázi je řádek s loginem a heslem zahashovaným do MD5. Pokud uživatel napíše login a heslo (v admin centru), heslo se převede do MD5 a zkontroluje se, jestli souhlasí s heslem v řádku toho daného uživatele (ten správný řádek se hledá přes sloupec login)... Pokud to souhlasí (a uživatel tedy znal správné jméno a heslo), vytvoří se několik session proměnných a uživatel je poté přesměrován již přímo do redakčního systému (viz níže).

Mezi session proměnné patří id uživatele (první sloupec v tabulce uživatelů), poté náhodný pěticiferný řetězec zahashovaný do MD5 (který se generuje při přihlášení a poté se uloží do DB - odtud se poté kontroluje), IP adresa uživatele (to samé - uloží se při přihlašování do databáze a odtud se porovnává s aktuální IP adresou uživatele - pokud nesouhlasí, okamžitě to usera vyhodí). A nakonec zbývá čas (pokud je aktuální čas větší jak čas posledního kliku + 30 minut, vyhodí vás to)...
Při přihlašování se tyto proměnné pouze vytvoří a při jednotlivých klicích již v samotném systému se porovnávají (porovnávání probíhá v souboru left.php, který obsahuje také menu - tento soubor je na začátku každého souboru v systému includován, takže se vše načítá s ním) a pokud něco nesouhlasí, uživatele to okamžitě vyhodí na logout.php, kde se jako první věc provádí unregister proměnných, když se tedy chce user vrátit zpátky, tak ho to tam nepustí, protože ty proměnné již neexistují a vyhodí ho to...

Tady je kontrola při přihlašování a vytváření session proměnných:
Kód: Vybrat vše
include('../_database_connect.php'); //připojení k databázi

$login_db=mysql_query("select * from nemesis_redaktori where login = '$zadany_login'") or die("Nelze SELECT: ".mysql_error());

$radek_redaktora=MySQL_Fetch_Array($login_db);

   //nastavování session údajů pro celou dobu přihlášení
   
   $id = $radek_redaktora["id"];

   //id_rand_md5
   $random1 = (rand()%9);
   $random2 = (rand()%9);
   $random3 = (rand()%9);
   $random4 = (rand()%9);
   $random5 = (rand()%9);
   $id_rand = $random1.$random2.$random3.$random4.$random5;
   $id_rand_md5 = md5("$id_rand");

   $ip=$REMOTE_ADDR;
      
      session_start();
      session_register("id");
      session_register("zadany_login");
      session_register("id_rand_md5");
      session_register("last_ip");
      session_register("last_time");

      $_SESSION["id"] = $id;
      $_SESSION["zadany_login"] = $zadany_login;
      $_SESSION["id_rand_md5"] = $id_rand_md5;

      mysql_query("
      update nemesis_redaktori set info_id_rand_md5 = '$id_rand_md5' where id = '$id'") or die("Nelze updatovat info_id_rand_md5. ERROR: ".mysql_error());
      
      $_SESSION["last_ip"] = $ip;

      mysql_query("
      update nemesis_redaktori set last_ip = '$ip' where id = '$id'") or die("Nelze updatovat last_ip. ERROR: ".mysql_error());

      $_SESSION["last_time"] = time();

      Header("Location: indexn.php?".SID); //okamžité hození na hlavní stránku redakčního systému
[/code]

A tady je
ovací část souboru left.php, který je includován na začátek každého souboru v redakčním systému, takže bez kontroly na začátku left.php se v redakčním systému nic nezobrazí:

Kód: Vybrat vše
session_start();

   include('../_database_connect.php');

   $zadany_login = $_SESSION["zadany_login"];

   $login_db=mysql_query("select * from nemesis_redaktori where login = '$zadany_login'") or die("Nelze SELECT:  ".mysql_error());

   $radek_redaktora=MySQL_Fetch_Array($login_db);

   $time = time();
   $last_time_plus_delay=$_SESSION["last_time"] + 1800;
   $ip = $REMOTE_ADDR;

   if($_SESSION["id"] != $radek_redaktora["id"])
      {
      Header("Location: logout.php?duvod=nekdo_jiny&".SID);
      }

   if($_SESSION["id_rand_md5"] != $radek_redaktora["info_id_rand_md5"])
      {
      Header("Location: logout.php?duvod=fatal&".SID);
      }

   if($ip != $radek_redaktora["last_ip"])
      {
      Header("Location: logout.php?duvod=jina_ip&".SID);
      }

   if($time > $last_time_plus_delay)
      {
      Header("Location: logout.php?duvod=time&".SID);
      }

   $_SESSION["last_time"] = $time;

//zde následuje už samotný RS, další podmínky nejsou potřeba, protože Header() to přesměrovává okamžitě...
//...
[/code]

A nak
čátek souboru logout.php:

Kód: Vybrat vše
   session_start();

   session_unregister("id");
   session_unregister("zadany_login");
   session_unregister("id_rand_md5");
   session_unregister("last_ip");
   session_unregister("last_time");

/*A potom tam už jsou jenom podmínky pro ten parametr "duvod", aby to vypsalo odpovídající hlášku ("Máte jinou IP", "Byl jste dlouho neaktivní", atd.)...*/
[/code]


Co se mi nezdá:
Pokud se odhlásím a chci jít zpátky na indexn.php (hlavní stránka RS), tak mi to v logout.php vyhodí jako duvod=time. Sice to tedy odhlásí, ale jaktože se mu líbí ty podmínky předtím??
Většinu údajů porovnávám s údaji v databázi, není to chyba?

Doufám tedy, že si tohle aspoň někdo přečte a věnuje mi svůj čas, aby objevil nějakou potenciální díru, kterou by neoprávněná osoba mohla zneužít.

Předem díky za odpovědi!
LastHunter
Junior

Odeslat příspěvekod AraxoN 11. 7. 2005 14:48

Podľa mňa sa ti zaradom spustia všetky podmienky (keďže medzi nimi nie je else, return, exit, die, alebo čokoľvek iné čo by narušilo inštrukčný tok). V HTTP hlavičkách sa objaví len ten posledný (tuším), lebo každé ďalšie volanie header("Location: ...") prepíše výsledok predošlého volania...

Jedna poznámočka - obsah session je uložený na servri, defaultne ako súbor ku ktorému má prístup len proces pod ktorým beží apache. Takže je úplne zbytočné porovnávať obsah session s riadkom v databáze, keďže jedno aj druhé má v správe výlučne tvoj PHP skript (ak vylúčime možnosť, že do databázy môže šahať aj niekto iný).

Poznámočka druhá: ak používaš $_SESSION, tak netreba session_register()
Podporujte baktérie - pre veľa ľudí je to jediná kultúra, ktorú majú.
AraxoN
Junior
Uživatelský avatar

Odeslat příspěvekod realdata.sk 11. 7. 2005 15:04

nieje to tym ze vsetky premenne $_SESSION si unset()-ol, a tym padom su prazdne .. a teda aj db query ti nevrati nic, a nasledny fetch tiez ...
takze tie podmienky ti porovnavaju neexistujuce sessionove premmene a neexistujucim polom .. a kedze neplati ze
"nic"!="nic" , tak to na tych prvych testoch nezbehne ...
:roll:
realdata.sk
Kolemjdoucí

Odeslat příspěvekod IgorK 11. 7. 2005 15:26

standardne sa session odloguje takto:
session_start();
// Unset all of the session variables.
session_unset();
// Finally, destroy the session.
session_destroy();

dobre clanky kedysi vysli na interval.cz:
http://interval.cz/serial.asp?serial=109

este jedna rada: na vypisanie premennych nemusis pouzivat cely zapis, napr. : <?php echo $foo; ?>, ale pouzivaj skrateny "echo" tvar: <?=$foo?> (obdoba JSP, ASP).
I own all your code - pay me all your money!
IgorK
Junior
Uživatelský avatar

Odeslat příspěvekod LastHunter 11. 7. 2005 15:28

AraxoN píše:Podľa mňa sa ti zaradom spustia všetky podmienky (keďže medzi nimi nie je else, return, exit, die, alebo čokoľvek iné čo by narušilo inštrukčný tok). V HTTP hlavičkách sa objaví len ten posledný (tuším), lebo každé ďalšie volanie header("Location: ...") prepíše výsledok predošlého volania...

Jedna poznámočka - obsah session je uložený na servri, defaultne ako súbor ku ktorému má prístup len proces pod ktorým beží apache. Takže je úplne zbytočné porovnávať obsah session s riadkom v databáze, keďže jedno aj druhé má v správe výlučne tvoj PHP skript (ak vylúčime možnosť, že do databázy môže šahať aj niekto iný).

Poznámočka druhá: ak používaš $_SESSION, tak netreba session_register()o/quote]

OK, takze po kazdym header(...) tam dam jeste exit();...
poznamecka - a s cim mam obsah session tedy porovnavat?
poznamecka druha - a funguje potom session_unregister() ?

realdata.sk píše:nieje to tym ze vsetky premenne $_SESSION si unset()-ol, a tym padom su prazdne .. a teda aj db query ti nevrati nic, a nasledny fetch tiez ...
takze tie podmienky ti porovnavaju neexistujuce sessionove premmene a neexistujucim polom .. a kedze neplati ze
"nic"!="nic" , tak to na tych prvych testoch nezbehne ...
:roll:


Nene, tak to neni, v databazi jsou ty posledni udaje furt ulozene...
LastHunter
Junior

Odeslat příspěvekod realdata.sk 12. 7. 2005 12:39

v databaze sice posledne udaje zostanu, ale ked ich nemas v $_SESSION , tak toto co mas na zaciatku ti nic v databaze nenajde, lebo $zadany_login bude prazdny (kedze $_SESSION["zadany_login"] si unsetol )

$zadany_login = $_SESSION["zadany_login"];

$login_db=mysql_query("select * from nemesis_redaktori where login = '$zadany_login'")
realdata.sk
Kolemjdoucí

Odeslat příspěvekod realdata.sk 12. 7. 2005 12:44

no a suhlasim aj s tym postupnych spracovanim IF-ov .... moze byt ze ti niekedy sadnu vsetky podmienky ,, ked su to rovnocenne podmienky za sebou bez else vetiev...

ale "V HTTP hlavičkách sa objaví len ten posledný (tuším), lebo každé ďalšie volanie header("Location: ...") prepíše výsledok predošlého volania... " toto nieje pravda , tam by ti vznikla hlavicka s niekolkymi Location za sebou ... Header() neprepisuje predosle svoje volania ....
realdata.sk
Kolemjdoucí

Odeslat příspěvekod AraxoN 12. 7. 2005 13:20

realdata.sk píše:... toto nieje pravda , tam by ti vznikla hlavicka s niekolkymi Location za sebou ... Header() neprepisuje predosle svoje volania ....


Práve som to overil:
-- súbor test.php --
Kód: Vybrat vše
<?php
        header("Location: index.php");
        header("Location: index2.php");
?>


-- požiadavka na HTTP server (Apache) --
Kód: Vybrat vše
GET /test.php HTTP/1.1
Host: localhost
...


-- odpoveď HTTP servra --
Kód: Vybrat vše
HTTP/1.1 302 Found
Date: Tue, 12 Jul 2005 11:18:00 GMT
Server: Apache-AdvancedExtranetServer/2.0.50 (Mandrakelinux/7mdk) mod_perl/1.99_16 Perl/v5.8.5 mod_ssl/2.0.50 OpenSSL/0.9.7d PHP/4.3.8
X-Powered-By: PHP/4.3.8
Location: index2.php
Content-Length: 0
Keep-Alive: timeout=15, max=97
Connection: Keep-Alive
Content-Type: text/html


Ako vidno, "Location:" sa v odpovedi objavil len raz. Možno to ale závisí od HTTP servra, alebo sa to tiež môže líšiť pre rôzne hlavičky. Odporúčam použiť Ethereal ak sú nejaké pochybnosti - mne to už párkrát pomohlo.
Podporujte baktérie - pre veľa ľudí je to jediná kultúra, ktorú majú.
AraxoN
Junior
Uživatelský avatar

Odeslat příspěvekod realdata.sk 12. 7. 2005 13:55

hmm... je mozne (pravdepodobne) ze viacnasobne volanie Header prepisuje atributy ako Location a podobne .. asi by to bolo logickejsie, ako keby ich davalo n-krat za sebou ...
ja som to myslel tak, ze volanie header("Pragma: ... ) neprepise Header("Location ....") ... :cry: (som to blbo napisal)
realdata.sk
Kolemjdoucí


Kdo je online

Uživatelé procházející toto fórum: Žádní registrovaní uživatelé a 0 návštevníků