PHP, jelszó

Üdv!

Elnézést, hogy ilyen kezdő kérdést teszek fel, de érdekel a nálam jobban hozzáértők véleménye.
A kérdés egyszerű: hogyan érdemes megoldani beléptetést PHP-val? Egyszerű login, semmi extra dolog.
A kérdés kicsit pontosabban:
1. Hogyan továbbítjuk biztonságosan a begépelt jelszót a szerverre?
2. Érdemes-e a webszerverre bízni (http authentication)?

Hozzászólások

Igen, ez is egy opció lehetne, hogy az egészet egy meglevő keretrendszerre bíznám, talán (vagy inkább biztos) ez lenne a legegyszerűbb.
Viszont ez a kis egyszerű (legalábbis remélem, hogy egyszerű) projekt a hasznos eszköz szerepen túl némi tanulás szerepet is játszana. Ez az egész projekt elég egyszerű ahhoz, hogy framework nélkül is meg lehessen oldani kevés szívással.

Először is leszögezném, hogy nincs általános válasz a kérdésedre, viszont van pár alap tipp, amit érdemes megfogadni.

1. Jelszó tárolás

A jelszó tárolás az első kritikus pontja a rendszerednek. Sok féle adatbázisban tudod tárolni a jelszavaidat, lehet az akár LDAP vagy MySQL, sima szövegfájl. Amire figyelni kell, az a jelszavak megfelelő titkosítása. Ez két dologből tevődik össze. Az egyik maga az egyirányú titkosító algoritmus, pl MD5, SHA1, SHA256, Blowfish, etc. Ezek közül olyat érdemes választani, ami lassú és nincs ismert kriptoanalízis rá (tehát az MD5 kiesett). A második fontos pont az úgynevezett sózás. Ez annyit tesz, hogy nem magát a jelszót hash-eled le, hanem hozzáteszel egy felhasználónként egyedi véletlen generált karaktersorozatot, amit plain textben is eltárolsz. Ennek az a célja, hogy megakadályozza a rainbow table jelszótörést arra az esetre, hogy kikerül az adatbázisod.

2. Beléptetés

Amikor belépteted a felhasználót az oldaladra, alapvetően érdemes a HTTP autentikációt elkerülni, mert ez minden egyes lekérdezésre elküldi a jelszót a szervernek. Ehelyett úgynevezett munkamenetet vagy sessiont érdemes használni. A session úgy működik, hogy a szerver oldalon létrehozol egy adatbázis bejegyzést vagy fájlt, amiben eltárolod az adott munkamenethez kapcsolódó adatokat, például azt, hogy be van-e lépve és ha igen, milyen felhasználónéven. Ezek után kiküldesz egy sütit (cookie-t), ami tartalmazza a session azonosítót. Itt érdemes arra figyelni, hogy ha nem létezik a szerver oldalon a felhasználó által küldött munkamenet azonosító, akkor mindenképpen érdemes újat generálni.

Amellett, hogy az adatbázisok használatánál természetesen vigyázni kell az SQL injection kivédésére, fokozottan kell ügyelni az XSS elleni helyes védelemre is, hiszen egy beillesztett JavaScript kóddal a munkamenet azonosító adott esetben könnyen ellopható.

Érdemes arra is figyelni, hogy a beléptető oldalad kizárólag HTTPS-en legyen elérhető és az megfelelően legyen konfigurálva. Ha a teljes oldal HTTPS-en elérhető, érdemes a sütinél a secure jelzőt és a fejlécekben a HSTS fejléceket beállítani. Emellett a sütiknél mindenképpen érdemes használni a httpOnly jelzőt.

3. Hosszú távú bejelentkezés

Ha olyan funkciót szeretnél beépíteni, mint a Facebook, hogy hosszú távon megjegyzi a böngészőt, akkor erre külön adatbázis bejegyzést kell létrehoznod a felhasználóhoz kapcsolva. Amikor az alkalmazásod meglátja ezt az azonosítót és nincs élő munkamenet, akkor megnézheto, hogy az adott kulcs melyik felhasználóhoz tartozik és automatikusan beléptetheti.

4. Véletlen karaktersorozatok generálása

Volt szó sok féle véletlen karaktersorozatról. Itt érdemes vagy a uniqid() függvényt vagy az openssl_random_pseudo_bytes() függvényt használni és a saját hákolásokat elkerülni.

5. Általános jótanácsok

Ha PHP-s alkalmazást gyártasz, érdemes a 10 évvel ezelőtti praktikákat és tévhiedelmeket elfelejteni. Mostanában már egy modern PHP-s alkalmazás OOP szemlélettel épül fel és megfelelően központosítja az adatbázis-kezelést valamint a beléptetést. Ez csökkenti a hibalehetőségeket és növeli a hatékonyságot. Erről bővebben a blogomon olvashatsz.

Remélem, segítettem. Ha valamivel kapcsolatban kérdésed van, állok rendelkezésedre.

--
Pásztor János
Üzemeltető Macik

A POST itt viszonylag keveset tesz hozza a dologhoz, bar teny, hogy sokkal szerencsesebb mint az URL-ben elkuldeni. Ha forgalmasabb az oldalad, mindenkeppen erdemes egy csuszoablakos IP limitet is tenni, hogy ne lehessen tobb ezer brute force probalkozast neki kergetni masodpercenkent.

--
Pásztor János
Üzemeltető Macik

Az egyes ponthoz egy rövid kiegészítés: a legbiztosabb, ha használod a PHP 5.5-ben megjelent, de egy compat php fájllal a korábbiakhoz is elérhető password_hash, password_needs_rehash, password_verify függvényeket. Ha terv szerint használod őket (létrehozáskor PASSWORD_DEFAULT-al hash, utána ellenőrzéskor verify és lekérdezed, hogy kell-e rehash, ha igen, akkor újra hasheled és elmented a DB-be), akkor a kódodtól függetlenül is fejlődhet a usereid biztonsága, ha frissítik a PHP verziót. - és ezzel gyakorlatilag mindent megcsináltál, amit janoszen írt az 1-es pontban.

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

Lehet ezeket hasznalni, bar sajnos pont a PHP core fejleszok azok, akik neha kepesek az ilyen dolgokat is elberhelni, lasd amikor megjelentek a filterek, mindenki lengette a ... zaszlot, hogy na ezt kell hasznalni $_GET, $_POST, stb helyett, aztan par honapra ra egy ordas nagy sechole figyelt pont ezekben a fuggvenyekben.

Nekem az a sokeves tapasztalatom, hogy minel kevesebbet hagysz a PHP-ra es minel inkabb kerulod az "egzotikus" funkciokat, annal uzembiztosabb lesz a kodod. Plane, hogy igy jol dokumentalt jelszo hashelesed van, amit adott esetben mas platformrol is tudsz hasznalni, amig mondjuk a PHP-s megoldas eseteben semmilyen garancia nincs arra, hogy "default" algoritmus ugyanaz marad a kovetkezo verzioban.

--
Pásztor János
Üzemeltető Macik

Semmilyen garancia nincs rá, sőt a doksi kifejezetten írja, hogy azt bármikor megváltoztathatják, ha a default már kevésnek bizonyulna. Ha nincs "másik oldal" (más nyelv, amihez crypto algo implementációt kellene keresni, hogy tudja ugyanazokat a hash-eket olvasni), akkor szvsz. ennyi "ismeretlen" belefér abba, hogy a kódomtól függetlenül "magától" javuljon a rendszerem biztonsága.

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

Jól értem, hogy az általad javasolt függvények gyakorlatilag a jelszavak titkosítását csinálják meg? Ha igen, akkor a "bármikor megváltoztathatják" csak okozhat gondot, hiszen a php által titkosított jelszavakat (mondjuk egy) adatbázisban tárolod. Ha változik a titkosító függvény, akkor elég nagy valószínűséggel az adatbázisban tárolt titkosított jelszavak nem fognak stimmelni.
Vagy félreértek valamit?

Hát, ha jól van ez kitalálva, attól ezek a függvények még működni fognak.

Akkor lehet szopás, ha te ezt egy másik alkalmazásodból, nem PHP-val is használnád. S ha egyszer megváltoztatják, akkor mehetsz doksit túrni, meg kódolni, hogy újra működjön minden. Míg, ha magad írod, ilyen gond sosem lesz.
--
blogom

Az ún. Modular Crypt Format-tal tárolják a hash-eket ($$ formában), így mindig lehet róluk tudni, hogy milyen algoritmussal készültek - ez alapján fogja tudni eldönteni a password_needs_rehash is, hogy kell-e újat készíteni (ill. pl. belenézhet, hogy megfelelően sokszor lett-e iterálva az algoritmus etc., de magát az algoritmust ebből nézheti meg).

BlackY
--
"en is amikor bejovok dolgozni, nem egy pc-t [..] kapcsolok be, hanem a mainframe-et..." (sj)

Azt hiszem, érteni vélem :)

Tehát elvileg PHP berkein belül (visszafele) kompatibilis a dolog, gond csak akkor kezdődhet, ha esetleg nem php-val akarod az adatokat kiszedni. Mondjuk ez a veszély (szerintem) nem fenyeget*, de azért biztos, ami biztos, kihagyom ezeket a függvényeket, ha jól értem, azok nélkül is teljesen jól meg lehet oldani.

* Volt azért már pár eset, mikor úgy gondoltam, hogy tuti nem, aztán mégis.

A második fontos pont az úgynevezett sózás. Ez annyit tesz, hogy nem magát a jelszót hash-eled le, hanem hozzáteszel egy felhasználónként egyedi véletlen generált karaktersorozatot, amit plain textben is eltárolsz.

Tehát ha mondjuk van uzsolt felhasználó, aki majd beléphet, választja jelszónak, hogy password, akkor generálok egy karaktersorozatot, mondjuk legyen az, hogy "kutyagumi", akkor a "kutyagumipassword"-ra csinálok egy sha256-ot, és ezt tárolom el mysql-ben. A kutyagumi szót meg kirakom pl. egy uzsolt.salt fájlba, és amikor valaki megpróbálkozik egy jelszóval, eljön a szerverre, elécsapom az uzsolt.salt fájl tartalmát, arra képzem az sha256-ot, és hasonlítom a mysql-ben tárolttal?

Nézd meg a crypt függvény leírását, talán segít jobban megérteni a dolgot! (nem tudom, szokás-e használni, szóval azt nem mondom, hogy használd is...)

Ez a sózás egyébként egy dologra jó ismereteim szerint: ha valaki illetéktelen hozzáfér a kódolt jelszavakat tartalmazó táblát, ne tudja ú.n. szivárványtáblák használatával megtalálni a kódolt jelszó eredetiét.

De a szakértő urak talán tudnak más okot is rá.

janoszen leírásából én is úgy látom, a sózás alapvetően adatbázistábla kikerülésekor jó.

Amennyire értelmezem, a crypt() is kb. ilyesmit csinál, csak amit janoszen is írt, egy picit "aggaszt" (tehát eléggé php-specifikus, tehát az adatbázis ezen részét más nyelven nemigen tudom használni).

Na, akkor miutan nincs kedvem egyesevel valaszolgatni:

- A PHP crypt implementacioja az alatta levo libeket hasznalja, ergo jol dokumentalt es crossplatform / crosslanguage
- A sot altalaban vagy magaba a jelszo hashbe taroljuk, mint pl a crypt, vagy mellette egy mezobe. Fajlba tarolni folosleges, mert a sok fajl menedzselese problemas lesz. A so mashol tarolasa nem segit sokat, mert elvben ha kikerul a DB-d, akkor minden kikerulhet.
- Egyebkent jol ertetted a lenyeget.

--
Pásztor János
Üzemeltető Macik

Rábízhatod az authtot a webszerverre is, és eldöntheted, hogy milyen backendet használsz - htpasswd, ldap, kerberos, mysql, stb.
Ha a PHP nem fér hozzá az auth adatokhoz, akkor nagyobb biztonságban vannak a jelszavak is.
--
PtY - www.onlinedemo.hu, www.westeros.hu

Előny:
- nem kell bajlódni a kódolással, csak lekérdezed a HTTP usert, és kész vagy, max. az ACL-eket kell kezelned saját db alapján.
- cserélheted a backendet (htpasswd-ről LDAP-ra vagy bármi másra, ahogy szeretnéd 0 kódolással).
- kombinálhatod a backendet autholhatsz több rendszerből is egyszerre pl. LDAP/htpasswd (szintén 0 kódolással).
Hátrány:
- nem lesz szép, egyedi auth formod
--
PtY - www.onlinedemo.hu, www.westeros.hu

Alapértelmezés: jelszót csak HTTPS alatt. És ebben a tekintetben mindegy, hogy hányszor utazik a dróton.
A PHP oldalnak a jelszót nem kell lekérni (bár komoly hátrány, hogy az adott session-ben a usernév mellett a jelszó is lekérdezhető PHP-ból, de ez a fejlesztő felelőssége.

--
PtY - www.onlinedemo.hu, www.westeros.hu