0

I've made a login script for my website and need some feedback as to whether it's secure or not and how I can improve it:

<?php
  session_start();

  $user = "root";
  $host = "localhost";
  $pass = "";
  $db = "test_db";

  $cxn = mysqli_connect($host, $user, $pass, $db) or die ("Couldn't connect to the server. Please try again.");

  if(isset($_POST['submit'])) {
    $username = mysql_real_escape_string(strip_tags(trim($_POST['username'])));
    $password = mysql_real_escape_string(strip_tags(trim($_POST['password'])));
    $message = "";

    $stmt = $cxn->prepare('SELECT * FROM users WHERE username = ?');
    $stmt->bind_param('s', $username);

    $stmt->execute();

    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
      if(password_verify($password, $row['password'])) {
        $_SESSION['username'] = $username;
        $_SESSION['password'] = $password;
        header("location:index.php");
      } else {
        $message = "The username or password is incorrect.";
      }
    }
}

?>

Also, I'm just learning about sessions and have a few questions:

  1. After the user logs in successfully, I need their username to show up on any other page. How can I make the sessions secure?
  2. How do I make the "log out" feature end the session?
Erik Fischer
  • 1,461
  • 3
  • 13
  • 16
  • 2
    This is already insecure. You aren't using parameterized queries. – ಠ_ಠ Aug 22 '13 at 17:17
  • The few questions belong here, but asking how to improve this belongs on [Code Review](http://codereview.stackexchange.com/) IMO – rink.attendant.6 Aug 22 '13 at 17:32
  • @BrendanLong, why is it 1 instead of 's'? – Erik Fischer Aug 22 '13 at 17:42
  • 1
    Good to see that somebody is using `password_hash()`! The while loop is not really helpful though, and do not store the password in the session. After `header()` it is recommended to always call `exit()`, otherwise the script continues to run. – martinstoeckli Aug 22 '13 at 20:06

3 Answers3

3

First of all, you shouldn't apply mysql_real_escape_string on the password field. You're escaping the string before the comparison with the hash stored in the database, so if the password contains specials characters like ' or ", they will get escaped. This will change the output hash, and the login won't work.

Take, for example, password pass123' will become pass123\', which will have a different hash.

Second, it's better if you don't store the plaintext password in the session either, as that means they get written (unencrypted by default) to a file on the disk; even if they're just server-side, if the server would get compromised, all logged-in accounts would be compromised, too. It's better if, after login, you just set the userid or an array/object with the logged-in user info. If that's present, you know the user is logged in, and to logout, you just have to unset the variable from the session.

Third, in order to be totally MySQLi risk-free, you should use prepared statements and PDO. For example:

<?php
    $dbh = new PDO("mysql:host=$host;dbname=$db;charset=utf8'", $user, $pass);

    if(isset($_POST['submit'])) {
        // Prepare the statement
        $stmt = $dbh->prepare("SELECT * FROM users WHERE username=:username");

        // Bind the parameters
        $stmt->bindParam(':username', $_POST['username'], PDO::PARAM_STR);

        // Execute the statement
        $stmt->execute();

        // Get the result
        $user= $stmt->fetch(PDO::FETCH_OBJ);

        if (empty($user) || !password_verify($_POST['password'], $user->password)) {
            $message = 'Login Failed';

        // Login is ok, store the user in the session
        } else {
            $_SESSION['loggedInUser'] = $user;

            $message = 'You are now logged in!';
        }
    }
grigger
  • 81
  • 1
  • 4
2
  1. Sessions are server sided, so you don't have to worry about security in that sense. You simply include session_start() at the beginning, and then echo $_SESSION['username']; whenever you want to.

  2. To end the session, use session_destroy().


Like I said in my comment, you need to use parameterized queries to actually be "secure" when creating your login script.

ಠ_ಠ
  • 3,060
  • 28
  • 43
1
$password = mysql_real_escape_string(strip_tags(trim($_POST['password'])));

Why are you removing whitespace and tags from the password? What if my randomly generated password was <correct horse battery stapl/>e? Your code would turn that password into e. This is all you should do with a password:

$password = mysql_real_escape_string($_POST['password']);

EDIT: Actually, you don't need to escape the password at all, since you're not using it in a query.


Regarding your edit:

$username = mysql_real_escape_string(strip_tags(trim($_POST['username'])));
$password = mysql_real_escape_string(strip_tags(trim($_POST['password'])));
$message = "";

$stmt = $cxn->prepare('SELECT * FROM users WHERE username = ?');
$stmt->bind_param('s', $username);

With parameterized queries, you don't need to escape anything (that's the big advantage), so this should be:

$username = trim($_POST['username']);
$password = $_POST['password'];
$message = "";

$stmt = $cxn->prepare('SELECT * FROM users WHERE username = ?');
$stmt->bind_param('s', $username);

For some good news, this may be the first Stack Overflow PHP question where I've seen someone handle passwords correctly:

if(password_verify($password, $row['password'])) {

YES.

Brendan Long
  • 53,280
  • 21
  • 146
  • 188
  • Thanks. Good catch, not sure why I did that. – Erik Fischer Aug 22 '13 at 17:37
  • When I try putting 1 in the bind_param, I get the following errors: **Warning**: mysqli_stmt::bind_param(): Undefined fieldtype 1 (parameter 2) in C:\xampp\htdocs\vidnut\login.php on line 17 **Fatal error**: Call to a member function fetch_assoc() on a non-object in C:\xampp\htdocs\vidnut\login.php on line 22 – Erik Fischer Aug 22 '13 at 17:49
  • 1
    Oops. I assumed PDO and MySQLi Were the same, but I guess I should never assume consistency when it comes to PHP.. – Brendan Long Aug 22 '13 at 19:38