这个php / mysql登录系统是否安全?

时间:2014-12-21 16:10:46

标签: php mysql security

我的登录系统不采用SELECT * FROM login WHERE user='$user' AND pass='$pass'的常规方法并对行进行计数,而是从数据库中提取密码,并检查输入的密码。我尝试了针对它的'or 1=1技巧,但它没有用。那么我有什么不安全感呢?

<?php
        $con = mysqli_connect("","root","mypassword","logins"); //Connect to MySql DB, in format host,user,password,db_name
        $query = mysqli_query($con,"SELECT Pass from login WHERE User='" . $_REQUEST["username"] . "'"); //Query that selects the password if the user equals the request string for "username"
        $row = mysqli_fetch_array($query); //Get the result in array form from the query
        if($_REQUEST["password"] == $row[0] && $row[0] != null){ /* Check if Password in the database equals the request string for the password */
                echo 'Login Sucsessful!';
        }
        else{
                echo 'Login failed!';
        }
?>

我的另一个问题是为什么大多数人都采用SELECT * FROM login WHERE user='$user' AND pass='$pass'方法,而不是这个。此外,这有什么不安全感吗?

4 个答案:

答案 0 :(得分:4)

密码加密方案

关于使用明文密码,请参阅其他答案。这不是一个好选择。 BCrypt(随时可以使用PHP)是可行的方法。

要真正偏执,你应该这样做,如果用户不在数据库中,则使用肯定错误的密码(例如浏览器用户提供的密码加一个字母)恢复假用户。然后运行密码检查所有相同的。通过强制所有用户的检查时间更长(无论是否存在),这可以防止用于剔除用户在系统中的时间(更长的检查时间)以及用户不是(快速,立即失败)的时间攻击。 / p>

利用性

一般情况下,接受用户输入而不检查/转义必须被视为始终不安全

在你的情况下,1=1漏洞无效,分号漏洞对MySQLi无害。

但是针对User注入了什么,并在POST中提供:

username=foo' limit 0 UNION select 'pwn3d!' AS Pass;--
password=pwn3d!

现在查询变为:

SELECT Pass from login WHERE User='foo' limit 0 UNION select 'pwn3d!' AS Pass;--';

或者如果有人想避免--;,理由是某些SQL分析可能会将查询拒绝为不合格(例如,因为它是多次查询,这种情况不常见或不受支持,并且无论如何可疑),这更复杂username

foo' LIMIT 0 UNION SELECT 'pwn3d!' AS Pass UNION SELECT Pass FROM login WHERE ''!='

将产生:

+--------+
| Pass   |
+--------+
| pwn3d! |
+--------+
1 row in set (0.00 sec)

当然,因为我们选择了密码,所以这总是等于我们提供的密码。

因此登录检查将通过

这是fiddle(删除SQL注释,SQLfiddle似乎不喜欢)。

这要求攻击者知道密码字段的名称,并且在现实世界中实施此攻击,如上所述,将无法正常工作(用户信息丢失)。但是根据实际的实现情况,可以调整它并实际以任何用户身份登录,前提是您知道他/她的登录名,甚至不知道密码是什么。

另一个有趣的可能性是将用户名的MD5哈希值与用户名一起存储。然后查询可以是

SELECT * FROM (
    SELECT * FROM login WHERE hashed_username=?
    UNION
    SELECT [list of default fields], ? AS Pass
) LIMIT 1;
无论用户是否存在,

总是返回一条记录; hashed_username提供的值md5($_REQUEST['username'])即使我们没有使用准备好的查询也会阻止任何SQL攻击,并且hashed_username上的索引可以保证定时攻击不可行。默认密码的提供值为BCrypt::hash("_NOT_".$_REQUEST['password'])

此时我们只运行BCrypt::verify($record['Pass'], $_REQUEST['password'])并查看它是返回true(用户是否存在,密码是OK)还是false(用户错误密码)。这两个执行路径现在足够接近,没有合理的数量的强制执行可以导致攻击者发现有效的用户名或密码。一个合适的BCrypt round值也可以保证破坏受损的数据库并不可行。

此时易受攻击的地方是密码强度。请记住,password policy如此复杂以至于用户认为将其写下来并将其粘贴到监视器上是实际或必要的,这实际上对安全性有害(在那里,做到了。叹气)。

答案 1 :(得分:1)

您绝不应将密码存储为纯文本。如果您的数据库遭到入侵,这样做可能会泄露您的所有用户信息。由于许多人对多个服务使用相同的密码,这可能是一个巨大的安全问题。

您应该按照评论中的建议查看密码哈希。

您也容易受到SQL注入攻击。切勿直接在查询中使用用户提供的信息,因为可以在用户名字段中编写可能会对数据库执行各种操作的特定字符串。

要解决此问题,请查看准备好的陈述。

答案 2 :(得分:1)

为了展示足够安全(对于大多数情况)登录脚本应该是什么样子,我从这个项目中学习了一些类的部分:php-login

以下是在数据库中存储密码的方法:

// crypt the user's password with the PHP 5.5's password_hash() function, results in a 60 character hash string
// the PASSWORD_DEFAULT constant is defined by the PHP 5.5, or if you are using PHP 5.3/5.4, by the password hashing
// compatibility library. the third parameter looks a little bit shitty, but that's how those PHP 5.5 functions
// want the parameter: as an array with, currently only used with 'cost' => XX.

$user_password_hash = password_hash($user_password, PASSWORD_DEFAULT, array('cost' => $hash_cost_factor));



// write new users data into database using PDO and of course prepared statements

 $query_new_user_insert = $this->db_connection->prepare('INSERT INTO users (user_name, user_password_hash, user_email, user_activation_hash, user_registration_ip, user_registration_datetime) VALUES(:user_name, :user_password_hash, :user_email, :user_activation_hash, :user_registration_ip, now())');
$query_new_user_insert->bindValue(':user_name', $user_name, PDO::PARAM_STR);
$query_new_user_insert->bindValue(':user_password_hash', $user_password_hash, PDO::PARAM_STR);
$query_new_user_insert->bindValue(':user_email', $user_email, PDO::PARAM_STR);
$query_new_user_insert->bindValue(':user_registration_ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR);
$query_new_user_insert->execute();
// id of new user
$user_id = $this->db_connection->lastInsertId();

请注意,我们只插入了密码的散列形式(password_hash),而不是密码本身 - &gt; 你永远不应该这样做!请注意,如果已经使用了用户名或电子邮件,则会进行检查。

您应该只插入密码的哈希值。您永远不应该插入未使用的密码

现在,如果我们想要登录用户,我们将再次使用password_verify和预备语句:

$query_user = $this->db_connection->prepare('SELECT * FROM users WHERE user_name = :user_email');
$query_user->bindValue(':user_name', trim($user_name), PDO::PARAM_STR);
$query_user->execute();
// get result row (as an object)
$result_row = $query_user->fetchObject();

// here we will check for the correct password / check if the password hash doesn't fit (mind the ! password_verify):  
if (! password_verify($user_password, $result_row->user_password_hash)) {

// increment the failed login counter for that user
$sth = $this->db_connection->prepare('UPDATE users '
. 'SET user_failed_logins = user_failed_logins+1, user_last_failed_login = :user_last_failed_login '
. 'WHERE user_name = :user_name OR user_email = :user_name');
$sth->execute(array(':user_name' => $user_name, ':user_last_failed_login' => time()));

在这种情况下,我们会回复邮件或将无法正确识别的用户重新定向到登录页面...

现在是事物匹配的部分:

    else {
// write user data into PHP SESSION [a file on your server]
$_SESSION['user_id'] = $result_row->user_id;
$_SESSION['user_name'] = $result_row->user_name;
$_SESSION['user_email'] = $result_row->user_email;
$_SESSION['user_logged_in'] = 1;
// declare user id, set the login status to true
$this->user_id = $result_row->user_id;
$this->user_name = $result_row->user_name;
$this->user_email = $result_row->user_email;
$this->user_is_logged_in = true;
// reset the failed login counter for that user
$sth = $this->db_connection->prepare('UPDATE users '
. 'SET user_failed_logins = 0, user_last_failed_login = NULL '
. 'WHERE user_id = :user_id AND user_failed_logins != 0');
$sth->execute(array(':user_id' => $result_row->user_id));

我们将为用户设置一个$ _SESSION并使用该$ _SESSION识别他/她,你可以在github上的库中看到这个。您现在也可以重定向用户。

您应该在登录区域内的每个页面加载时检查登录/设置的SESSION。

答案 3 :(得分:0)

我曾经做过你正在做的事情,但我很快就意识到它在许多情况下有多糟糕,并且是与SQL数据库通信最安全的方式之一。

This website是我使用PDO连接通过PHP连接SQL的方法。我不记得他们在我的头顶,所以这是我的书签,并证明是非常方便。我使用并喜欢PDO连接,但是你可以使用其他一些方法来实现类似的功能。

此外,就密码使用而言,Daniel表示以纯文本格式存储密码 BAD 。使用某种形式的加密作为必须。最好的是使用函数password_hash()password_verify()的bcrypt。使用起来非常简单,并了解它的工作原理。 Here's a previously asked StackOverflow question非常好地展示了如何使用bcrypt来哈希密码。


长话短说:

- 使用PDO连接,以防止SQL注入
Tutorial from tutsplus.com

- 切勿以纯文本格式存储密码。使用某种形式的散列来存储它们 Past StackOverflow answer

这是您需要立即创建安全数据库习惯的入门包!