The site I developed was recently compromised, most likely with brute force or a Rainbow Table attack. There was no SAL in the original log-in script, passwords were saved in MD5.
The following is an updated script that includes a ban on SALT and IP addresses. In addition, it will send Mayday mail and SMS and disconnect the account if the same IP address or attempted account 4 failed login. Please review it and let me know what can be improved, what is lacking and what is just strange.
<?php
session_start();
include $_SERVER['DOCUMENT_ROOT'] . '/includes/pdo_conn.inc.php';
$errmsg_arr = array();
$errflag = false;
function clean($str) {
$str = @trim($str);
if(get_magic_quotes_gpc()) {
$str = stripslashes($str);
}
return $str;
}
define('SALT', '63Yf5QNA');
$login = clean($_POST['login']);
$password = clean($_POST['password']);
$encryptedPassword = md5(SALT . $password);
$ip_address = $_SERVER['REMOTE_ADDR'];
$checkIPBan = $db->prepare("SELECT COUNT(*) FROM ip_ban WHERE ipAddr = ? OR login = ?");
$checkIPBan->execute(array($ip_address, $login));
$numAttempts = $checkIPBan->fetchColumn();
if ($numAttempts == 1) {
$getTotalAttempts = $db->prepare("SELECT attempts FROM ip_ban WHERE ipAddr = ? OR login = ?");
$getTotalAttempts->execute(array($ip_address, $login));
$totalAttempts = $getTotalAttempts->fetch();
$totalAttempts = $totalAttempts['attempts'];
if ($totalAttempts >= 4) {
$to = "admin@somewhere.com";
$subject = "Banned Account - $login";
$mailheaders = 'From: noreply@somewhere.com' . "\r\n";
$mailheaders .= 'Reply-To: noreply@somewhere.com' . "\r\n";
$mailheaders .= 'MIME-Version: 1.0' . "\r\n";
$mailheaders .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
$msg = "<p>IP Address - " . $ip_address . ", Username - " . $login . "</p>";
mail($to, $subject, $msg, $mailheaders);
$setAccountBan = $db->query("UPDATE ip_ban SET isBanned = 1 WHERE ipAddr = '$ip_address'");
$setAccountBan->execute();
$errmsg_arr[] = 'Too Many Login Attempts';
$errflag = true;
}
}
if($login == '') {
$errmsg_arr[] = 'Login ID missing';
$errflag = true;
}
if($password == '') {
$errmsg_arr[] = 'Password missing';
$errflag = true;
}
if($errflag) {
$_SESSION['ERRMSG_ARR'] = $errmsg_arr;
session_write_close();
header('Location: http://somewhere.com/login.php');
exit();
}
$loginSQL = $db->prepare("SELECT password FROM user_control WHERE username = ?");
$loginSQL->execute(array($login));
$loginResult = $loginSQL->fetch();
if($loginResult['password'] == $encryptedPassword) {
session_regenerate_id();
$getMemDetails = $db->prepare("SELECT * FROM user_control WHERE username = ?");
$getMemDetails->execute(array($login));
$member = $getMemDetails->fetch();
$_SESSION['SESS_MEMBER_ID'] = $member['user_id'];
$_SESSION['SESS_USERNAME'] = $member['username'];
$_SESSION['SESS_FIRST_NAME'] = $member['name_f'];
$_SESSION['SESS_LAST_NAME'] = $member['name_l'];
$_SESSION['SESS_STATUS'] = $member['status'];
$_SESSION['SESS_LEVEL'] = $member['level'];
$_SESSION['SESS_LAST_LOGIN'] = $member['lastLogin'];
$updateLog = $db->prepare("UPDATE user_control SET lastLogin = DATE_ADD(NOW(), INTERVAL 1 HOUR), ip_addr = ? WHERE user_id = ?");
$updateLog->execute(array($ip_address, $member['user_id']));
session_write_close();
if ($numAttempts > 0) {
$deleteIPBan = $db->prepare("DELETE FROM ip_ban WHERE ipAddr = ?");
$deleteIPBan->execute(array($ip_address));
}
if ($member['level'] != "3" || $member['status'] == "Suspended") {
header("location: http://somewhere.com");
} else {
header('Location: http://somewhere.com');
}
exit();
} else {
if ($numAttempts < 1) {
$addBanEntry = $db->prepare("INSERT INTO ip_ban (ipAddr, login, attempts) VALUES (?,?,?)");
$addBanEntry->execute(array($ip_address, $login, 1));
} else {
$updateBanEntry = $db->prepare("UPDATE ip_ban SET ipAddr = ?, login = ?, attempts = attempts+1 WHERE ipAddr = ? OR login = ?");
$updateBanEntry->execute(array($ip_address, $login, $ip_address, $login));
}
header('Location: http://somewhere.com/login.php');
exit();
}
?>
EDIT
Ok, here is my attempt at random salt. First create a salt to be inserted into the table:
define('SALT_LENGTH', 15);
function createSalt()
{
$key = '!@#$%^&*()_+=-{}][;";/?<>.,';
$salt = substr(hash('sha512',uniqid(rand(), true).$key.microtime()), 0, SALT_LENGTH);
return $salt;
}
$salt = createSalt()
Then generate a random salt hash:
$hash = hash('sha256', $salt . $pw); //$pw is the cleaned user submitted password
When the user logs in, compare the stored hash with the stored randomly generated salt:
$loginHash = hash('sha256', $dbSalt . $pw);
if ($loginHash == $dbHash) {
//Logged in
} else {
//Failed
}
?