Variante Profi
Die Variante Profi legt Wert auf einen möglichst hohen Sicherheitgrad. Manche der hier gezeigten Dinge sind auf Mietwebspace meistens, falls überhaupt, nur bei sehr guten Providern möglich, da sie ein Anpassen der php.ini erfordern. Schauen wir uns zunächst einmal das Listing der Datei login_profi.php an. Danach erläutere ich die einzelnen Schritte und binde eigene Funktionen mit in die Erklärung ein. Sämtliche Funktionen wurden in die Datei funktionen.inc.php ausgelagert. Das komplette Script gibt es am Ende des Tutorials noch einmal als Download zum selber testen. Inhalt der Datei login_profi.php:
PHP-Code:
<?php
// Fehlermeldungen unterdrücken
error_reporting( 0 );
// Erzwingen das Session-Cookies benutzt werden und die SID nicht per URL transportiert wird
ini_set( 'session.use_only_cookies', '1' );
ini_set( 'session.use_trans_sid', '0' );
// Session starten
session_start();
// Sicherstellen das die SID durch den Server vergeben wurde
// um einen möglichen Session Fixation Angriff unwirksam zu machen
if (!isset( $_SESSION['server_SID'] ))
{
// Möglichen Session Inhalt löschen
session_unset();
// Ganz sicher gehen das alle Inhalte der Session gelöscht sind
$_SESSION = array();
// Session zerstören
session_destroy();
// Session neu starten
session_start();
// Neue Server-generierte Session ID vergeben
session_regenerate_id();
// Status festhalten
$_SESSION['server_SID'] = true;
}
// Funktionen einbinden
include( 'funktionen.inc.php' );
// Variablen deklarieren
$_SESSION['angemeldet'] = false;
$conid = '';
$eingabe = array();
$anmeldung = false;
$update = false;
$fehlermeldung = '';
// Datenbankverbindung öffnen
$conid = db_connect();
// Wenn das Formular abgeschickt wurde
if (isset( $_POST['login'] ))
{
// Benutzereingabe bereinigen
$eingabe = cleanInput();
// Benutzer anmelden
$anmeldung = loginUser( $eingabe['benutzername'], $eingabe['passwort'], $conid );
// Anmeldung war korrekt
if ($anmeldung)
{
// Benutzer Identifikationsmerkmale in DB speichern
$update = updateUser( $eingabe['benutzername'], $conid );
// Bei erfolgreicher Speicherung
if ($update)
{
// Auf geheime Seite weiterleiten
header( 'location: geheim_profi.php' );
exit;
}
else
{
$fehlermeldung = '<h3>Bei der Anmeldung ist ein Problem aufgetreten!</h3>';
}
}
else
{
$fehlermeldung = '<h3>Die Anmeldung war fehlerhaft!</h3>';
}
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>phpBuddy.eu - Login Script</title>
</head>
<body>
<?php
// Falls die Fehlermeldung gesetzt ist
if ($fehlermeldung) echo $fehlermeldung;
?>
<!-- Hier steht die Login-Form -->
</body>
</html>
Als erstes setzen wir das Error Reporting auf 0, damit sämtliche Fehlermeldungen unterdrückt werden. So gut Fehlermeldungen während der Entwicklung sind, so schädlich können sie im live Einsatz sein. Fehlermeldungen verraten einem Angreifer sehr viel über die Struktur einer Datei, Datenbank, Funktionsweise des Scripts und erleichtern somit seine Arbeit.
Die nächsten beiden Zeilen betreffen die php.ini. Damit wird erzwungen, dass die Session Kennung via Session Cookie transportiert wird. Auf diese Weise kann man keine Referrer mit sichtbarer Kennung in fremden Logfiles hinterlassen, was zu gestohlenen Sessions (Session Hijacking) führen kann. Ob und in welchem Umfang man Zugriff auf die php.ini hat, ist von Provider zu Provider unterschiedlich. Manche erlauben ein verändern der Werte via
ini_set() oder via
.htaccess, während wieder andere Provider eigene php.ini Dateien direkt im Verzeichnis erlauben oder man kann über die Accountverwaltung Werte anpassen. Die Frage ob und wie das bei Euch möglich ist kann nur euer Provider beantworten!
Dann folgt das obligatorische starten der Session. Da ein Benutzer mit einer bereits aktiven Session auf unsere Seite kommen kann und unser
session_start() in diesem Fall lediglich die Session fortführen würde, müssen wir sicherstellen, dass die Session auch tatsächlich zuvor von unserem Server initiiert wurde und nicht aus fremder Quelle übergeben wurde. Dieses "aus fremder Quelle" übergeben einer Session Kennung an ein potentielles Opfer nennt sich
Session Fixation. Ein Angreifer kann eine Session nur dann übernehmen/stehlen, wenn die Session Kennung bekannt ist. Da der Angreifer diese Kennung selbst erzeugt haben kann und an sein Opfer weitergegeben hat, kennt er die Kennung natürlich! Um das zu verhindern stellen wir sicher, dass nur eine von uns selbst vergebene Kennung Gültigkeit hat.
Dazu prüfen wir, ob in der Session die Variable
$_SESSION['server_SID'] den Wert true hat. Trifft das nicht zu, resetten wir die Session, wandeln die Session in ein leeres Array um, zerstören die Session, starten die Session komplett neu, generieren eine neue Kennung (dem
session_regenerate_id() fällt eine besonders wichtige Rolle zu, auf die ich weiter unten noch detaillierter eingehen werde) und setzen letztendlich den Wert der Variable
$_SESSION['server_SID'] auf true.
Durch das starten, vollständige zerstören, neu starten der Session stellen wir sicher, dass unser Script gegen Session Fixation abgesichert ist!
Jetzt binden wir die benötigten Funktionen ein, bereiten einige Variablen vor und öffnen eine Datenbankverbindung, deren Handle wir in
$conid ablegen.
Wir prüfen ob das Forumlar abgeschickt wurde und falls ja, bereinigen wir erst einmal die Benutzereingabe.
PHP-Code:
$eingabe = cleanInput();
Die dazugehörige Funktion sieht so aus:
PHP-Code:
function cleanInput()
{
// Maskierende Slashes aus POST Array entfernen
if (get_magic_quotes_gpc())
{
$eingabe['benutzername'] = stripslashes( $_POST['benutzer'] );
$eingabe['passwort'] = stripslashes( $_POST['passwort'] );
}
else
{
$eingabe['benutzername'] = $_POST['benutzer'];
$eingabe['passwort'] = $_POST['passwort'];
}
// Trimmen
$eingabe['benutzername'] = trim( $eingabe['benutzername'] );
$eingabe['passwort'] = trim( $eingabe['passwort'] );
// In Kleinschrift umwandeln
$eingabe['benutzername'] = strtolower( $eingabe['benutzername'] );
// Eingabe zurückgeben
return $eingabe;
}
Große Erklärungen sollten nicht nötig sein. Die Eingabe wird aufbereitet und ein Array mit dem Benutzername und Passwort zurückgeliefert, wonach im öffentlichen Bereich des Scripts fortan das Array
$eingabe zur Verfügung steht.
Nun wird geprüft, ob ein entsprechender Benutzer in unserer Datenbank existiert.
PHP-Code:
$anmeldung = loginUser( $eingabe['benutzername'], $eingabe['passwort'], $conid );
Die Funktion
loginUser() erwartet 3 Parameter: 1) Benutzername 2) Passwort 3) Verbindungskennung. Die Rückgabewert wird in
$anmeldung abgelegt und kann entweder
true (Benutzer gefunden) oder
false (Login fehlerhaft) sein.
Diese Funktion
loginUser() ist der größte Brocken im ganzen Script, also werfen wir mal einen Blick auf das, was in der Funktion passiert.
PHP-Code:
function loginUser( $benutzer, $passwort, $conid )
{
// Anweisung zusammenstellen
$sql = "SELECT
`passwort_zusatz`
FROM
`login_profi`
WHERE
LOWER(`benutzername`) = '" .mysql_real_escape_string( $benutzer ). "' AND
`aktiviert` = 1";
// Anweisung an DB schicken
$ergebnis = mysql_query( $sql, $conid );
// Wurde ein Datensatz gefunden, existiert dieser Benutzername, also
// prüfen wir ob die Anmeldedaten korrekt ist
if (mysql_num_rows($ergebnis) == 1)
{
$datensatz = mysql_fetch_array( $ergebnis );
// Resourcen freigeben
mysql_free_result( $ergebnis );
// Anmeldepasswort vorbereiten
$zusatz = $datensatz['passwort_zusatz'];
$anmeldepw = md5( $passwort.$zusatz );
// Anweisung zusammenstellen
$sql = "SELECT
`id`, `fehlversuche`
FROM
`login_profi`
WHERE
LOWER(`benutzername`) = '" .mysql_real_escape_string( $benutzer ). "' AND
`passwort` = '" .mysql_real_escape_string( $anmeldepw ). "' AND
`aktiviert` = 1";
// Anweisung an DB schicken
$ergebnis = mysql_query( $sql, $conid );
// Prüfen ob ein Datensatz gefunden wurde. In dem Fall stimmen die Anmeldedaten
if (mysql_num_rows( $ergebnis ) == 1)
{
// Counter für Fehlversuche resetten
$angriff = mysql_fetch_array( $ergebnis );
if ($angriff['fehlversuche'] != 0)
{
$sql = "UPDATE
`login_profi`
SET
`fehlversuche` = 0
WHERE
LOWER(`benutzername`) = '" .mysql_real_escape_string( $benutzer ). "'
LIMIT
1";
mysql_query( $sql, $conid );
}
// Resourcen freigeben
mysql_free_result( $ergebnis );
// Korrekte Anmeldung zurückgeben
return true;
}
else
{
// Das angegebene Passwort war nicht korrekt, also gehen wir von einem Angriffsversuch aus
// und erhöhen den Counter der fehlerhaften Anmeldeversuche
$sql = "UPDATE
`login_profi`
SET
`fehlversuche` = `fehlversuche` + 1
WHERE
LOWER(`benutzername`) = '" .mysql_real_escape_string( $benutzer ). "'
LIMIT
1";
mysql_query( $sql, $conid );
// Abfragen ob das Limit von 10 Fehlversuche erreicht wurde und in diesem Fall ...
$sql = "SELECT
`fehlversuche`
FROM
`login_profi`
WHERE
LOWER(`benutzername`) = '" .mysql_real_escape_string( $benutzer ). "'";
$ergebnis = mysql_query( $sql, $conid );
$anzahl = mysql_fetch_array( $ergebnis );
mysql_free_result( $ergebnis );
// ... das Konto deaktivieren
if ($anzahl['fehlversuche'] > 9)
{
$sql = "UPDATE
`login_profi`
SET
`fehlversuche` = 0,
`aktiviert` = 0
WHERE
LOWER(`benutzername`) = '" .mysql_real_escape_string( $benutzer ). "'
LIMIT
1";
mysql_query( $sql, $conid );
}
}
}
}
Als erstes stellen wir eine Anweisung zusammen mit der wir das Feld
passwort_zusatz auslesen, das zum Datensatz des Benutzername gehört und welches aktiviert=1 hat. Der Benutzername muß in der DB vom Typ Unique sein, damit es hier keine Überschneidungen gibt. Dieser Passwort-Zusatz wird auch Salt (eng. Salz) genannt und dient dazu Passwörter sicherer zu machen. Dieses Salt muß für jeden Benutzer einmalig zufällig erstellt werden (beim anlegen des Benutzer) und wird in der Datenbank abgelegt. Wurde von der DB ein Treffer zurückgeliefert wissen wir, das der Benutzername in unserem System existiert. Aus der Kombination eingegebenes Passwort + Salt aus der DB erzeugen wir einen md5-Hash, der unser tatsächliches Anmeldepasswort darstellt.
Klären wir noch schnell die Frage "Was soll das mit dem Salt?".
Wenn mehrere Benutzer das selbe Passwort haben, so ist der md5-Hash immer identisch. Kennt man also ein Passwort mit zugehörigem Hash und sieht irgendwo diesen Hash wieder weiß man, dass der andere Benutzer das selbe Passwort verwendet. Der Angreifer hat also keinerlei Arbeit sich Zugang zu einem Account zu verschaffen. Um es den Angreifern noch einfacher zu machen gibt es sogenannte
Rainbow Tables, in denen etliche Millionen Hashes gespeichert sind, mit denen man binnen Sekunden die echte Identität eines Passwort ermitteln kann. Weil wir aber einen zufälligen Salt mit in's Passwort mischen, ist der Hash jedesmal anders, selbst wenn das reine Passwort identisch ist.
Lange Rede kurzer Sinn: Passwörter anhand ihres Hash zu erraten oder mithilfe von Rainbow Tables zurückzuwandeln ist unmöglich, wenn man Passwörter mit einem geheimen Salt versieht und den damit erzeugten Hash in der DB ablegt.
Passwörter sollten niemals im Klartext gespeichert werden!
Zurück zum Script ...
Wir haben jetzt also das Salt ausgelesen und unser Anmeldepasswort erzeugt. Wir schicken jetzt eine neue Anweisung an die Datenbank und lesen das Feld
fehlversuche aus, das zum Datensatz gehört auf den der Benutzername + Anmeldepasswort passt. Haben wir einen Treffer, stimmen die Anmeldedaten offensichtlich überein und es handelt sich hier um unseren User. Wir resetten noch kurz den Wert von
fehlversuche, falls dieser nicht 0 ist und melden mit
return true; die erfolgreiche Anmeldung an's Hauptscript.
Haben wir bei der Abfrage mit dem Benutzername + Passwort keinen Treffer erzielt wissen wir, dass zwar der Benutzername stimmt, nicht aber das Passwort! Da wir auf Sicherheit setzen, gehen wir also vom Schlimmsten aus und denken erst mal, dass es sich um einen Angriff handelt. Deswegen aktualisieren wir im else-Zweig das Feld
fehlversuche und erhöhen den Counter um 1. Anschließend lesen wir das Feld
fehlversuche erneut aus um zu sehen, ob das Limit Fehlversuche (in unserem Fall 10) erreicht wurde. Da sich vermutlich kein Mensch 10 mal beim eigenen Passwort verschreibt, hat hier jemand versucht mit Brute Force das Passwort zu knacken. Um dem einen Riegel vorzuschieben, deaktivieren wir das Benutzerkonto, indem wir
aktiviert auf 0 setzen. Jetzt kann unser Angreifer so lange probieren wie er möchte, er wird sich nicht einloggen können, selbst wenn das Passwort korrekt wäre. Wer aufgepasst hat wird gesehen haben, dass wir bei jeder DB Abfrage immer den
aktiviert Status mit einbezogen haben. Ist das Konto deaktiviert, kann man gar nichts machen, bis das Konto vom Admin wieder aktiviert wurde.
Wir haben nun also geklärt ob die eingegebenen Benutzerdaten korrekt waren oder nicht, oder ob jemand versucht hat das Konto zu hacken. Demnach haben wir im Hauptscript in
$anmeldung jetzt ein
true oder
false stehen. Im Falle von true geht's weiter mit
PHP-Code:
$update = updateUser( $eingabe['benutzername'], $conid );
In der Funktion
updateUser() findet die eigentliche Anmeldung statt. Zusätzlich speichern wir noch verschiedene Daten, anhand deren wir den Benutzer während des Aufenthalts auf unserer Seite identifizieren.
PHP-Code:
function updateUser( $benutzer, $conid )
{
// Benutzer-Datensatz aktualisieren
$sql = "UPDATE
`login_profi`
SET
`ip` = '" .mysql_real_escape_string( $_SERVER['REMOTE_ADDR'] ). "',
`benutzerinfo` = '" .mysql_real_escape_string( $_SERVER['HTTP_USER_AGENT'] ). "',
`anmeldung` = '" .mysql_real_escape_string( md5( $_SERVER['REQUEST_TIME'] ) ). "',
`zuletzt_aktiv` = NOW()
WHERE
LOWER(`benutzername`) = '" .mysql_real_escape_string( $benutzer ). "'
LIMIT
1";
mysql_query( $sql, $conid );
// Prüfen ob der datensatz aktualisiert wurde
if (mysql_affected_rows( $conid ) == 1)
{
// Session Variablen setzen
$_SESSION['angemeldet'] = true;
$_SESSION['benutzername'] = $benutzer;
$_SESSION['anmeldung'] = md5( $_SERVER['REQUEST_TIME'] );
return true;
}
}
Wie wir sehen aktualisieren wir den Benutzerdatensatz und speichern neben der IP Adresse auch die Identifikationsmerkmale des Browsers (Type, OS, Build Version, usw.). Da die IP Adresse generell eher ungeeignet ist als Identifikationsmerkmal, und auch die Browserkennung nicht einmalig ist, speichern wir auch den md5-Hash der Anmeldezeit. Dieser Wert dürfte, in Verbindung mit dem Benutzername, ziemlich zuverlässig sein. Wurde der Datensatz erfolgreich aktualisiert, findet erst jetzt die eigentliche Anmeldung statt!
Nun setzen wir die Session Variablen
$_SESSION['angemeldet'] auf
true, sowie den Benutzername und die Anmeldezeit und liefern ein
true in's Hauptscript zurück. Anschließend leiten wir den Benutzer mit einer header()-Anweisung zur geheimen Seite weiter.
Wichtig: Wenn Fehler auftreten, sei es falsches Passwort oder der Benutzername ist unbekannt, teilen wir lediglich mit
das bei der Anmeldung ein Fehler aufgetreten ist, nicht aber
welcher! Würden ein Angreifer versuchen den Account mit dem Name Peterchen zu hacken und das System meldet "Benutzer unbekannt", bräuchte der Angreifer gar nicht weiter versuchen. Wieso sollte er auch einen Account hacken, der gar nicht existiert?! Versucht er nun sein Glück mit dem Account Fritzchen und auf einmal taucht die Meldung "Passwort ist falsch" auf, wo worher immer "Benutzer unbekannt" stand, weiß der Angreifer, dass dieses Konto tatsächlich existiert und kann sich an's knacken des Passworts machen.
Okay, der Benutzer ist nun auf der geheimen Seite geheim_profi.php:
PHP-Code:
<?php
ini_set( 'session.use_only_cookies', '1' );
ini_set( 'session.use_trans_sid', '0' );
// Session starten
session_start();
// Funktionen einbinden
include( 'funktionen.inc.php' );
// Datenbankverbindung öffnen
$conid = db_connect();
// Benutzer prüfen
if (!checkUser( $conid ))
{
resetUser();
}
// Benutzer abmelden
if ($_GET['benutzer'] == 'abmelden')
{
resetUser();
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>phpBuddy.eu - Geheime Seite</title>
</head>
<body>
<h3>Willkommen im geschützten Bereich! ;-)</h3>
<p><a href="<?php echo $_SERVER['PHP_SELF']. "?benutzer=abmelden"; ?>">Benutzer abmelden</a></p>
</body>
</html>
Der obere PHP Teil, der so auf jeder (!) geschützten Seite stehen muß, ist bereits vertraut und bedarf (hoffentlich) keiner Worte!
Hier kontrollieren wir mithilfe von
PHP-Code:
if (!checkUser( $conid ))
ob der Benutzer sich auf dieser Seite aufhalten darf. Die Funktion
checkUser() hat folgenden Inhalt:
PHP-Code:
function checkUser( $conid )
{
// Alte Session löschen und Sessiondaten in neue Session transferieren
session_regenerate_id( true );
if ($_SESSION['angemeldet'] !== true) return false;
// Benutzerdaten aus DB laden
$sql = "SELECT
`ip`, `benutzerinfo`, `anmeldung`, UNIX_TIMESTAMP(`zuletzt_aktiv`) as zuletzt_aktiv
FROM
`login_profi`
WHERE
`benutzername` = '" .mysql_real_escape_string( $_SESSION['benutzername'] ). "' AND
`aktiviert` = 1";
$ergebnis = mysql_query( $sql, $conid );
if (mysql_num_rows( $ergebnis ) == 1)
{
$benutzerdaten = mysql_fetch_array( $ergebnis );
// Resourcen freigeben
mysql_free_result( $ergebnis );
// Daten aus der DB mit den Benutzerdaten vergleichen
if ($benutzerdaten['ip'] != $_SERVER['REMOTE_ADDR']) return false;
if ($benutzerdaten['benutzerinfo'] != $_SERVER['HTTP_USER_AGENT']) return false;
if ($benutzerdaten['anmeldung'] != $_SESSION['anmeldung']) return false;
if (($benutzerdaten['zuletzt_aktiv'] + 600) <= $_SERVER['REQUEST_TIME']) return false;
}
else
{
return false;
}
// Letzte Aktivität aktualisieren
$sql = "UPDATE
`login_profi`
SET
`zuletzt_aktiv` = NOW()
WHERE
LOWER(`benutzername`) = '" .mysql_real_escape_string( $_SESSION['benutzername'] ). "'
LIMIT
1";
mysql_query( $sql, $conid );
// Status zurückgeben
return true;
}
Das erste das in der Funktion geschieht ist
PHP-Code:
session_regenerate_id( true );
Diese Zeile ist ungemein wichtig! Diese PHP Funktion sorgt dafür, dass eine neue Session Kennung initiiert wird und der Inhalt der alten Session in die neue übernommen wird. Der Parameter
true existiert erst seit PHP 5.2.x und sorgt dafür, dass nicht nur eine neue Kennung generiert wird, sondern es werden auch sämtliche alten Reste der vorherigen Session gelöscht. Das erhöht die Sicherheit von Sessions deutlich, da es nicht mehr möglich ist, dass sich Unbefugte über einen Shell-Zugriff die alte Session Datei auf dem Server aneignen und auslesen und so u.U. an geheime Informationen gelangen.
Als nächstes wird geprüft, ob der Benutzr mit
$_SESSION['angemeldet'] angemeldet ist. Nun lesen wir die gespeicherten Vergleichsmerkmale aus der DB aus und vergleichen jeden dieser Punkte mit dem aktuell angemeldeten Benutzer. Stimmt ein Wert nicht überein, liefern wir
false zurück. In der Zeile
PHP-Code:
if (($benutzerdaten['zuletzt_aktiv'] + 600) <= $_SERVER['REQUEST_TIME']) return false;
prüfen wir zusätzlich, wie lange die letzte Aktivität auf der Seite zurückliegt. Wurde für 10 Minuten keine Aktivität registriert, wird der Benutzer zwangsabgemeldet. Das macht man deswegen, weil es Benutzer gibt die sich anmelden und dann auf anderen Seiten surfen oder den Rechner verlassen, während der Browser geöffnet bleibt. Das Problem dabei ist, dass die Session erst erlischt, wenn der Browser geschlossen wird. Mit anderen Worten: der Benutzer bleibt so lange angemeldet, bis er den Browser schließt, selbst wenn er schon lange nicht mehr auf der Seite war. Es könnte also jemand die Session übernehmen, während die Session aktiv ist. Durch das 10 Minuten Limit begrenzen wir das Zeitfenster in dem Schaden entstehen könnte.
Wurde die Funktion bisher nicht durch ein
return false; verlassen, handelt es sich um den echten Benutzer und wir aktualisieren den Datensatz dahingehend, dass wir die Zeit der letzten Aktivität festhalten.
Kommen wir zur letzten Funktion. Wurde ein
false zurückgeliefert oder der Benutzer klickt den "Abmelden"-Link, wird die Funktion
resetUser() aufgerufen:
PHP-Code:
function resetUser()
{
session_destroy();
header( 'location: login_profi.php' );
exit;
}
Hier geschieht nichts spektakuläres. Es wird lediglich der Inhalt der Session gelöscht und auf die Login Seite umgeleitet. Auf der Login Seite angekommen treten wieder unsere Anmelde- und Schutzmechanismen in Aktion.
Hinweis:
Noch ein Wort zu den Erkennungsmerkmalen eines Benutzers. Während die Browserkennung als zusätzliches Merkmal durchaus brauchbar ist, auch wenn viele den selben Browser und OS verwenden, gibt es doch erstaunlich häufig Abweichungen in SP Nummern, Build Versionen, etc., so ist die IP in den meisten Fällen ungeeignet!
Zum einen kann es sein das Benutzer durch Proxies surfen und sich somit ständig die IP ändert (z.B. AOL User benutzen zwangsweise Proxies, weil AOL es seinen Benutzern diktiert!), zum anderen sind speziell in Firmen meistens NAT Netzwerke installiert, wodurch sich eine handvoll bis mehrere Hundert oder Tausend Benutzer eine identische IP teilen. Man sollte sich also überlegen, ob man die IP zur Identifikation benutzen möchte!
Auf jeden Fall sollte aber noch etwas einmaliges zur Identifizierung genommen werden. In unserem Beispiel ist das der Benutzername in Kombination mit dem Hash der Anmeldezeit.
Fazit
Damit kommen wir zum Ende dieses Tutorials. Es wurde mal wieder länger als geplant, aber beim Thema Sicherheit sollte man das verzeihen können.

Bleibt zu hoffen, dass der ein oder andere etwas damit anfangen kann und die eigene Seite damit etwas sicherer wird. Um Missverständnisse auszuschließen hier noch der Hinweis:
Dieses Tutorial soll als Grundlage für eigene Login System dienen und ist nicht dazu gedacht, dass es genau so 1:1 übernommen wird! Ebenso geschieht die Benutzung auf eigene Gefahr und weder das Traum-Projekt noch der Autor dieses Tutorials können für Schäden, die durch das Script entstanden sein könnten, haftbar gemacht werden!
Zum Abschluß noch einige Links zu den verwendeten Funktionen und weiterführende Informationen zum Thema Sessions und Sicherheit.
Linkübersicht
Sessions und Sicherheit
PHP Funktionen