这个PHP/MySQL登录脚本安全吗?更新代码
您好 我设计的一个网站今天遭到破坏,目前正在进行破坏控制。未经授权访问了两个用户帐户,包括主管理员。请看一看正在使用的登录脚本,如能了解安全漏洞,将不胜感激。我不确定这是否是一个SQL注入,或者可能是一台过去用于访问此区域的计算机上的漏洞 谢谢这个PHP/MySQL登录脚本安全吗?更新代码,php,security,login,Php,Security,Login,您好 我设计的一个网站今天遭到破坏,目前正在进行破坏控制。未经授权访问了两个用户帐户,包括主管理员。请看一看正在使用的登录脚本,如能了解安全漏洞,将不胜感激。我不确定这是否是一个SQL注入,或者可能是一台过去用于访问此区域的计算机上的漏洞 谢谢 <?php //Start session session_start(); //Include DB config require_once('config.php'); //Error message
<?php
//Start session
session_start();
//Include DB config
require_once('config.php');
//Error message array
$errmsg_arr = array();
$errflag = false;
//Connect to mysql server
$link = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
if(!$link) {
die('Failed to connect to server: ' . mysql_error());
}
//Select database
$db = mysql_select_db(DB_DATABASE);
if(!$db) {
die("Unable to select database");
}
//Function to sanitize values received from the form. Prevents SQL injection
function clean($str) {
$str = @trim($str);
if(get_magic_quotes_gpc()) {
$str = stripslashes($str);
}
return mysql_real_escape_string($str);
}
//Sanitize the POST values
$login = clean($_POST['login']);
$password = clean($_POST['password']);
//Input Validations
if($login == '') {
$errmsg_arr[] = 'Login ID missing';
$errflag = true;
}
if($password == '') {
$errmsg_arr[] = 'Password missing';
$errflag = true;
}
//If there are input validations, redirect back to the login form
if($errflag) {
$_SESSION['ERRMSG_ARR'] = $errmsg_arr;
session_write_close();
header("location: http://somewhere.com");
exit();
}
//Create query
$qry="SELECT * FROM user_control WHERE username='$login' AND password='".md5($_POST['password'])."'";
$result=mysql_query($qry);
//Check whether the query was successful or not
if($result) {
if(mysql_num_rows($result) == 1) {
//Login Successful
session_regenerate_id();
//Collect details about user and assign session details
$member = mysql_fetch_assoc($result);
$_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'];
//Get Last Login
$_SESSION['SESS_LAST_LOGIN'] = $member['lastLogin'];
//Set Last Login info
$qry = "UPDATE user_control SET lastLogin = DATE_ADD(NOW(), INTERVAL 1 HOUR) WHERE user_id = $member[user_id]";
$login = mysql_query($qry) or die(mysql_error());
session_write_close();
if ($member['level'] != "3" || $member['status'] == "Suspended") {
header("location: http://somewhere.com");
} else {
header("location: http://somewhere.com");
}
exit();
}else {
//Login failed
header("location: http://somewhere.com");
exit();
}
}else {
die("Query failed");
}
?>
我不是SQL专家,但我知道在数据库中存储密码(或密码散列)通常不是一个好的安全实践。通常更安全的做法是使用密码加密文件,然后通过表单接受密码,并尝试使用密码哈希对其进行解密。这样,;服务器硬盘上永远不会存储关于密码本身的任何信息,没有密码,任何人都无法解密文件(或数据库条目),即使他们可以物理访问服务器或数据库文件。您可以在存储的密码中添加盐,以防止字典和彩虹表攻击
您还可以将密码存储在更强的散列/加密散列中,而不是MD5
例如,请查看:
如果尝试登录失败次数过多,也可以临时挂起IP地址。比如说一个小时
这样可以防止暴力攻击。这并不是真的阻止它,至少让它变得更加困难。这绝不是全面的,可能包括一般审查的内容:
- 连接失败时的
调用调用die()
,这可能会泄漏敏感信息(数据库主机、用户名)mysql\u error()
- 通常,请始终尝试将第二个参数(连接句柄)传递给
,这样可以确保该值相对于连接的字符集等正确转义mysql\u real\u escape\u string()
调用包含字符串“location”;正确的标题名称为“位置”header()
- 您可以清理
,然后使用$\u POST['password']
;虽然(由于md5($\u POST['password'])将其重新注入SQL中
的行为)这不会引入漏洞,但在打开md5()
的环境中,它可能会出现漏洞,只是有点不一致get\u magic\u quotes\u gpc()
- 您对
的调用没有指定连接句柄-尽管在这个脚本中,不太可能有另一个句柄,我喜欢在与mysql交谈时显式地说-它给了我温暖的模糊感mysql\u query()
不想超出这个答案的范围,进行过多的推测;会话劫持可能是罪魁祸首吗?(我不知道会话是如何恢复的,但您的代码可能“信任”会话数据似乎是合理的。当然,这在某些情况下并不容易克服,例如,将会话绑定到IP地址是一个良好的开端。)这适用于具有多个用户的站点。密码存储在MD5加密中,并在登录时加密,比较两个MD5加密的密码。您根本不应该加密用户密码——您应该对它们进行加密和散列。哈希不能反转,适当的盐分会使暴力攻击失效,或使彩虹表的密码正确地强。一般来说,暴力攻击不会因为添加盐分而受到伤害。因为它可以尝试随机凭证,直到找到正确的凭证为止。看起来该站点包含用户登录凭证的MySQL数据库表没有被破坏,该个人只是登录到该站点的CMS。这让我想到了SQL注入的可能性,尽管使用中的脚本应该阻止这种情况。第二个被泄露的帐户属于从未使用过系统的人,这很奇怪。我认为Erik的意思是对密码哈希本身进行暴力攻击,这使得对salted(“nonced”)哈希的攻击更加困难,这也是香草彩虹表变得无用的原因。有一个非常简单的解决方案来对付SQL注入:存储过程。通过使用存储过程,攻击者可以使用多种类型的字符串,它只会使用与预期完全相同的语句执行。当然,这并不能防止业务错误,如暴露的cookie、会话接管等。但它避免了与手动构建SQL语句相关的所有问题。无论你创造了什么,它都永远不会安全。尽最大努力不要成为“低垂的果实”,并密切关注所有数据访问点。就脚本而言,我强烈建议尽可能地学习/使用
准备好的语句
(又称存储过程
),因为它不仅可以防止SQL注入,还可以提高数据访问率(数据库缓存等)。我还想添加:+1,以提高安全性。作为一个开发人员,它显示了一种成熟的水平,即愿意识别您不知道的东西,以及另一种成熟的水平,即寻求帮助。非常感谢您的智慧之言。看起来这段代码的升级时间已经过去很久了,我为自己没有掌握这段代码而自责。但我想这是何
<?php
//Start session
session_start();
//Include DB config
include $_SERVER['DOCUMENT_ROOT'] . '/includes/pdo_conn.inc.php';
//Error message array
$errmsg_arr = array();
$errflag = false;
//Function to sanitize values received from the form. Prevents SQL injection
function clean($str) {
$str = @trim($str);
if(get_magic_quotes_gpc()) {
$str = stripslashes($str);
}
return $str;
}
//Define a SALT
define('SALT', 'heylookitssuperman');
//Sanitize the POST values
$login = clean($_POST['login']);
$password = clean($_POST['password']);
//Encrypt password
$encryptedPassword = md5(SALT . $password);
//Input Validations
//Obtain IP address and check for past failed attempts
$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 there are 4 failed attempts, send back to login and temporarily ban IP address
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) {
//Send Mayday SMS
$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 there are input validations, redirect back to the login form
if($errflag) {
$_SESSION['ERRMSG_ARR'] = $errmsg_arr;
session_write_close();
header('Location: http://somewhere.com/login.php');
exit();
}
//Query database
$loginSQL = $db->prepare("SELECT password FROM user_control WHERE username = ?");
$loginSQL->execute(array($login));
$loginResult = $loginSQL->fetch();
//Compare passwords
if($loginResult['password'] == $encryptedPassword) {
//Login Successful
session_regenerate_id();
//Collect details about user and assign session details
$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'];
//Get Last Login
$_SESSION['SESS_LAST_LOGIN'] = $member['lastLogin'];
//Set Last Login info
$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 there are past failed log-in attempts, delete old entries
if ($numAttempts > 0) {
//Past failed log-ins from this IP address. Delete old entries
$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 {
//Login failed. Add IP address and other details to ban table
if ($numAttempts < 1) {
//Add a new entry to IP Ban table
$addBanEntry = $db->prepare("INSERT INTO ip_ban (ipAddr, login, attempts) VALUES (?,?,?)");
$addBanEntry->execute(array($ip_address, $login, 1));
} else {
//increment Attempts count
$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();
}
?>