Ich hab da so n PHP Security-Buch gelesen (findeste auf amazon.de, "PHP-Sicherheit" heissts). Dadurch bin ich überhaupt so "gut" informiert, bzw. mein Sicherheitsdenken ist dadurch geweckt empfiehlt sich das mal anzuschaffen, auch wenns nicht unbedingt billig ist.
Okay, jedenfalls bin ich da stark ins grübeln gekommen. Man sagt, man solle auf die PHPSession-Funktion zurückgreifen und KEINE Userdaten im Cookie speichern. im selben buch steht aber auch, dass man per XSS-Angriffe (im hauptsächlichen durch einbringen von HTML-Code in die Datenbank) Cookies auslesen kann - und die SessionID von PHP wird als Cookie gespeichert. Du wirst nun wohl sehen worauf ich hinaus will: Kann man XSS durchführen, kann man sich die SessionIDs erschleichen und sich selbst ein Cookie setzen - und zack, hat man womöglich sogar zugriff auf einen Adminaccount. Fatal.
Ich hab mir nun die Frage gestellt: Wie kann ich eine möglichst sichere Session gestalten, ohne dass Userdaten wie Username und das md5(passwort) beim User per Cookie gespeichert werden?
Da bin ich nach einiger Zeit des Überlegens auf die Idee gekommen - mach eine eigene Session. Gesagt getan.
Was wird benötigt?
- Einen Code-Generator. Ich habe mir da einen geschmiedet, der einen 128bit Schlüssel erstellt. dürfte eigentlich ziemlich unmöglich sein, den zu knacken, da er aus insgesamt 3 teilen besteht (2x 50 bit, 1x 28 bit, sprich 3 zufallsparameter. Lässt sich aber beliebig erhöhen wie man will, man kann auch 12 zufallszahlen generieren und diese aneinanderreihen)
- Ein zusätzliches Feld in der Userdatenbank, namens sessionid. Hierin wird der generierte Sessioncode gespeichert.
- Ein Cookie, z.B. mit dem Namen cmsname_sessionid und als value die eigene 128bit sessionid
- Die PHP-Session. Warum? Ganz einfach. Bei erfolgtem Login werden sämtliche Userdaten AUSSER das PW in die $_SESSION-Variable gelegt. Die ist erstens ständig verfügbar solange die PHP-Session besteht, und zweitens brauchen wir die um einen Timestamp der Zeit des erfolgten Logins zu erstellen.
Das ist also die Grundbasis.
Nun brauchen wir eine Funktion, die überprüft ob der User existiert. Dies macht sie, in dem sie die Datenbank connektiert. Die vom Benutzer übergebenen Daten werden NICHT in dieser Abfrage erwähnt. Es wird eine Abfrageschleife gestartet, die die Usernamen abgleicht. Beispiel:
Code:
PHP-Code:
while($getUserData = mysql_fetch_object($query)) {
if(md5($getUserData->username) == md5($_POST['username'])){
weitere prüfungen...
Warum bei Usernamen md5 einsetzen? ist n zuverlässiger Check ob die groß/kleinschreibung beachtet wurde
Gibt es dann den User und stimmt das Passwort, löst diese Funktion die eigentliche Session-Funktion aus. Diese macht nun folgendes:
1.: Generierung des SessionCodes
2.: Speichern des Sessioncodes im Cookie (MIT! Sessionverfallsdatum!)
3.: Speichern des Sessioncodes in der Datenbank
4.: Starten der PHP-Session
5.: Speichern aller Userdaten ausser dem PW in der $_SESSION-Variable
6.: Speichern des Logintimestamps in der $_SESSION-Variable.
Okay, das alles nutzt aber noch gar nichts. Es wird eine SessionStart-Funktion benötigt Hier wird NICHT die startsession() funktion von PHP genutzt, sondern wieder eine eigene. die wird am ANFANG jeder PHP-Datei gesetzt. diese macht dann folgendes:
1.: Ist der sessioncookie mit 128Bits vorhanden?
2.: wenn nein - juckt uns nicht, dann wird eben nichts gemacht und zur Sicherheit $_SESSION = array(); und session_destroy(); ausgeführt. schaden kann es nicht.
3.: wenn der Cookie existiert, wird ersteinmal überprüft, ob schon eine PHP-Session existiert.
4.: wenn JA - dann wird überprüft - und aha - ob der $_SESSION['timestamp'] ÄLTER ist als die aktuelle zeit - abzüglich 60 sekunden!
5.: wenn dies zutrifft wird die eigene Session regeneriert - sprich ein neuer code wird generiert, im cookie und in der Datenbank gespeichert. der user bekommt dies gar nicht mit.
6.: falls punkt 3. nicht zutrifft, checken wir, ob es diesen Sessioncode in der Datenbank gibt.
7.: Falls ja, dann wird der User wieder eingeloggt, sprich neue PHP-Session erstellt und Userdaten hineingeladen sowie auch wieder ein Timestamp erstellt.
Sooo, okay. Man kann nun ja auch Cookies manipulieren, deswegen brauchen wir noch eine Funktion, die auch checkt, ob der Cookie gültig ist. Er muss aus 128 zeichen bestehen, darf keine Sonderzeichen beinhalten und vor allem keine script-parameter. Falls der Cookie manipuliert ist -> die(); und hackversuch loggen. PHPSession-Cookie sollte man auch prüfen, und zwar auf vorhandensein eines int-Values, sprich dieser Cookie darf nur aus 12 zeichen und ausschliesslich aus zahlen bestehen.
Jetzt brauchen wir noch eine Logout-Funktion. Diese Funktion macht die Session in der Datenbank ungültig (durch einen nicht-128bit-schlüssel, denn es sind ja NUR 128Bits zugelassen), killt den eigenen Sessioncookie und killt die Variable $_SESSION. Zudem setzt er in der usertabelle das feld "loggedin" auf 0.
Der einzige Unsicherheitsfaktor ist die Lebensdauer des Sessioncookies mit der selbst erstellten Session-ID. Wenn ein User dauerhaft eingeloggt sein will, muss die Lebensdauer des Cookies erhöht werden. Kann man ja auch auf 7 Tage begrenzen oder was auch immer, selbst dann ist es eher nicht möglich per Bruteforce-Attacken die Sessionid zufällig bei einem nicht-ausgeloggten User zu finden. Doch je länger der Cookie mit dem zuletzt benutztem Sessionkey existiert, desto höher ist die Wahrscheinlichkeit, dass der Account gehighjacked werden kann. Allerdings ist die Wahrscheinlichkeit auch nich allzu hoch, geh ich mal davon aus.
Wichtig ist natürlich auch alle Abfragen vor manipulierten Daten zu schützen, aber das versteht sich, denk ich mal, nun auch von selbst
Zusammengefasst schauts so aus:
- Userlogin wird nicht direkt, sondern per Schleife aus der Datenbank gelesen
- PHPSession zum temporären Speichern der Userdaten, erspart später unnötige MySQL-Connections.
- Eigene Sessionid mit >= 128bits, die sich alle 60 sekunden neu generiert
- Secure-Logout: Komplette Session wird zerstört, in der Datenbank sowie auch bei den Cookies und der $_SESSION Variable
So, das war reichlich Text, aber im Groben spiegelt das mein eigenes Loginscript wider. Und ich denk mal, dass es wohl eines der Sichersten ist, die ich persönlich kenne.