Da das Login System von mrhappiness nun schon ein paar Jahre alt, und somit nicht mehr auf dem aktuellsten Stand ist, habe ich mich mal an einem remake versucht. Wollte ursprünglich nur ein paar Änderungen vornehmen, doch am Ende habe ich es von Grund auf neugeschrieben. Vorweg, ich bin PHP Anfänger (bzw. habe mich mal für ein paar Tagen vor mehr als 4 Jahren damit beschäftigt).
Könnt ihr mir helfen die grössten Sicherheitslücken zu stopfen? Sticht da was ins Auge was die Logik oder die Sicherheit angeht?
Ideen, Vorschläge und Kritik ist selbstverständlich willkommen.
Grundlegende Änderungen (plus meine Gedanken dazu):
- Als MySQL Schnittstelle wird nun PDO anstatt mysql_verwendet
- Anstatt md5 wird sha1 verwendet
- Passwörter werden zusätzlich mit einem Salt gespeichert
- Cookie Funktion für automatische Anmeldung hinzugefügt. (alle Informationen im Cookie werden mit sha1 gehasht und beinhalten einen Salt. Anstatt das gehashte Passwort im Cookie zuspeichern, wurde ein extra CookieSession Feld in der DB angelegt.)
- Logout Funktion keine seperate Datei mehr, sonder über $_GET['logout'] aufrufbar.
Habe versucht den Code ein bisschen zu dokumentieren, und falls der Codeschnipsel nicht von mir stammt habe ich die Quelle angegeben.
login.php
functions.inc.php
Hier die dazugehörige SQL Struktur (inkl. 3 Test Users, admin, test, test2 - Username und Passwort jeweils gleich)
UserID // Selbsterklärend
username // Selbsterklärend
password // sha1 ($EingabeDesUsers.$Salt)
salt //Zufällig generierter 40 Stelliger String
session // aktuelle session_id()
cookiesession // sha1 (Zufällig generierter 40 Stelliger String)
email // Selbsterklärend
Freundliche Grüsse
Londrag
Könnt ihr mir helfen die grössten Sicherheitslücken zu stopfen? Sticht da was ins Auge was die Logik oder die Sicherheit angeht?
Ideen, Vorschläge und Kritik ist selbstverständlich willkommen.
Grundlegende Änderungen (plus meine Gedanken dazu):
- Als MySQL Schnittstelle wird nun PDO anstatt mysql_verwendet
- Anstatt md5 wird sha1 verwendet
- Passwörter werden zusätzlich mit einem Salt gespeichert
- Cookie Funktion für automatische Anmeldung hinzugefügt. (alle Informationen im Cookie werden mit sha1 gehasht und beinhalten einen Salt. Anstatt das gehashte Passwort im Cookie zuspeichern, wurde ein extra CookieSession Feld in der DB angelegt.)
- Logout Funktion keine seperate Datei mehr, sonder über $_GET['logout'] aufrufbar.
Habe versucht den Code ein bisschen zu dokumentieren, und falls der Codeschnipsel nicht von mir stammt habe ich die Quelle angegeben.
login.php
PHP-Code:
<?php
session_start();
include_once('functions.inc.php');
$username = $_POST['username'];
$password = $_POST['password'];
$autologin = $_POST['autologin'];
$cookieusername = $_COOKIE['cookieusername'];
$cookiesession = $_COOKIE['cookiesession'];
try {
//Open MySQL Connection with PDO
$dbh = new PDO('mysql:host='.$dbhost.';dbname='.$dbname.'', $dbuser, $dbpass);
//If logout is requested
if ($_GET['logout'] === "yes") {
logout();
$cookieusername = NULL;
$cookiesession = NULL;
echo 'Sie wurden erfolgreich ausgeloggt.<br />';
}
//If logged in
if (IsLoggedIn() === true) {
echo 'Sie sind zurzeit eingeloggt.<br /><a href="?logout=yes">Logout</a>';
}
//If Cookie was found and verified as true, login user
elseif (isset($cookieusername) && isset($cookiesession)) {
$UserID = VerifyCookie($cookieusername, $cookiesession);
//If cookie information incorrect, error message and logout user to delete cookie
if ($UserID === false) {
logout();
echo 'Automatische Anmeldung durch Cookie fehlgeschlagen.<br />
Sie werden nun ausgeloggt und die Cookies werden gelöscht</br />';
}
//If cookie information okay, login user
else {
LoginUser($UserID, $autologin);
echo 'Willkommen zurück. Sie wurden automatisch durch ihre Cookies eingeloggt.<br />';
}
}
//Login, continue only if Username and Password are not empty
elseif (isset($_POST['login']) && $username !== "" && $password !== "") {
$UserID = VerifyUser($username, $password);
//If login incorrect, error message
if ($UserID === false) {
echo 'Anmeldung fehlgeschlagen.<br />';
}
//If login okay, login user
else {
LoginUser($UserID, $autologin);
echo 'Sie wurden erfolgreicht eingeloggt.<br />';
}
}
//If not logged in, display login form
if (IsLoggedIn() === false) {
echo '<form method="post" action="login.php">
Username: <input type="text" name="username" id="username" /><br />
Password: <input type="password" name="password" id="password" /><br />
Angemeldet bleiben: <input type="checkbox" name="autologin" id="autologin" /><br />
<input name="login" type="submit" value="Login">
</form>';
}
//Close MySQL Connection
$dbh = NULL;
}
catch(PDOException $e) {
echo $e->getMessage();
}
?>
PHP-Code:
<?php
//Database login information
$dbhost = "HOSTNAME";
$dbname = "DATABASE NAME";
$dbuser = "USERNAME";
$dbpass = "PASSWORD";
//Disbaling Magic Quotes, code from http://de.php.net/manual/de/security.magicquotes.php#76387
if (get_magic_quotes_gpc()) {
function stripslashes_deep($value)
{
$value = is_array($value) ?
array_map('stripslashes_deep', $value) :
stripslashes($value);
return $value;
}
$_POST = array_map('stripslashes_deep', $_POST);
$_GET = array_map('stripslashes_deep', $_GET);
$_COOKIE = array_map('stripslashes_deep', $_COOKIE);
$_REQUEST = array_map('stripslashes_deep', $_REQUEST);
}
//Check if logged in
function IsLoggedIn() {
global $dbh;
$sql='SELECT session FROM users WHERE session="'.session_id().'"';
$row=$dbh->query($sql)->fetch();
if (session_id() === $row['session']) {
return true;
}
else {
return false;
}
}
//Veryfing POST_Username and POST_Password
function VerifyUser($username, $password) {
global $dbh;
$sql = 'SELECT UserID, password, salt FROM users WHERE username = :username';
$stmt = $dbh->prepare($sql);
$stmt->bindParam(':username', $username, PDO::PARAM_STR, 40);
$stmt->execute();
$row = $stmt->fetch();
if ($row['password'] === sha1($password.$row[salt])) {
return $row[UserID];
}
else {
return false;
}
}
//Logging in User
function LoginUser($UserID, $autologin) {
global $dbh;
$sql='UPDATE users SET session="'.session_id().'" WHERE UserID = "'.$UserID.'"';
$dbh->exec($sql);
//Set a Cookie and Update DB with CookieSession
if ($autologin == true) {
$sql = 'SELECT username, salt FROM users WHERE UserID = "'.$UserID.'"';
$row = $dbh->query($sql)->fetch();
$cookiesalt = sha1(GenerateSalt());
$usernamesalt = sha1($row['username'].$row['salt']);
setcookie("cookieusername", $usernamesalt, time() + 31536000, "/");
setcookie("cookiesession", $cookiesalt, time() + 31536000, "/");
$sql = 'UPDATE users SET cookiesession="'.$cookiesalt.'" WHERE UserID = "'.$UserID.'"';
$dbh->exec($sql);
}
}
//Creating Salt with 40 characters, code from http://wiki.jumba.com.au/wiki/PHP_Generate_random_password
function GenerateSalt() {
$characters = "01234567890abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_!?-+";
$i = 0;
$salt = "";
while ($i <= 40) {
$salt .= $characters{mt_rand(0,strlen($characters))};
$i++;
}
return $salt;
}
//Verify Cookie
function VerifyCookie($cookieusername, $cookiesession) {
global $dbh;
$sql = 'SELECT UserID, username, salt, cookiesession FROM users WHERE cookiesession = :cookiesession';
$stmt = $dbh->prepare($sql);
$stmt->bindParam(':cookiesession', $cookiesession, PDO::PARAM_STR, 40);
$stmt->execute();
$row = $stmt->fetch();
if ($cookiesession === $row['cookiesession'] && $cookieusername === sha1($row['username'].$row['salt'])) {
return $row[UserID];
}
else {
return false;
}
}
//Logout Users, delete cookie and set session & cookiesession field in DB to NULL
function Logout() {
global $dbh;
setcookie("cookieusername", "", time() - 31536000, "/");
setcookie("cookiesession", "", time() - 31536000, "/");
$dbh->exec('UPDATE users SET session="'.NULL.'", cookiesession = "'.NULL.'" WHERE session = "'.session_id().'"');
}
?>
Hier die dazugehörige SQL Struktur (inkl. 3 Test Users, admin, test, test2 - Username und Passwort jeweils gleich)
Code:
CREATE TABLE `users` ( `UserID` int(11) unsigned NOT NULL auto_increment, `username` varchar(40) NOT NULL, `password` varchar(40) NOT NULL, `salt` varchar(40) NOT NULL, `session` varchar(32) default NULL, `cookiesession` varchar(40) default NULL, `email` varchar(255) NOT NULL, PRIMARY KEY (`UserID`), UNIQUE KEY `username` (`username`,`email`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
Code:
INSERT INTO `users` (`UserID`, `username`, `password`, `salt`, `session`, `cookiesession`, `email`) VALUES (1, 'admin', '67440a4fc2736f883108ae1c69dab0606222e0cb', 'FUgRO;1txF,Q;,2qyyR0.yHPOPVTfzegVAIR?ZKc', '', '', 'admin@admin.com'), (2, 'test', '4307bbda209f29f932e8f0a05baf7dc8f8a3015b', '!tWcGU1GyHW68N-4LcZdHg9XhW!FR,KdzgY6nORV', '', '', 'test@test.de'), (3, 'test2', '79475350f67f95b32f18a3dfb071af4cc6f20f96', 'ER0E6tiP0vNMoz0aOLt6hU2wYE,V!Bg6eEQf7iz;', '', '', 'test2@test2.de');
username // Selbsterklärend
password // sha1 ($EingabeDesUsers.$Salt)
salt //Zufällig generierter 40 Stelliger String
session // aktuelle session_id()
cookiesession // sha1 (Zufällig generierter 40 Stelliger String)
email // Selbsterklärend
Freundliche Grüsse
Londrag
Kommentar