1

Can you experts give me some thougths on this code? Some security hole i have missed? Can you see any potential threats? Something i can do better?

I'm still learning :) Thanks

<?php

if (isset($_POST['username'])) {

$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
$password2 = mysql_real_escape_string($_POST['password2']);
$encrypted_password = md5($password);


// remove eventuakl space
foreach($_POST as $key => $val) $_POST[$key] = trim($val);


// check if username is taken
$query = mysql_query("SELECT COUNT(*) FROM users WHERE username = '$username'");
if (mysql_result($query, 0) > 0) {
$reg_error[] = 0;
}

// make sure username only cosist of at least 3 letters, numbers or _ -
if (!preg_match('/^[a-zA-Z0-9_-]{3,}$/', $username)) {
$reg_error[] = 4;  
}


// check for empty fields
if (empty($username) || empty($password) || empty($password2)) {
$reg_error[] = 2;
}

// check if the passwords match
if ($password != $password2) {
$reg_error[] = 3;
}

// save if error is unset
if (!isset($reg_error)) {
mysql_query("INSERT INTO users (username, password, registered, registration_ip)
             VALUES('$username', '$encrypted_password', '".time()."', '".$_SERVER['SERVER_ADDR']."')");

$_SESSION['id'] = mysql_insert_id();
header('refresh: 3; url=/home');

}


}
?>

Login.php

if (isset($_POST['username'])) {

$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
$md5_password = md5($password); 

$query = mysql_query("SELECT id FROM users WHERE username = '$username' and password = '$md5_password'");


if (mysql_num_rows($query) == 0) {
header("Location: ".$_SERVER['REQUEST_URI']."");
exit;
}

// set session
$_SESSION['id'] = mysql_result($query, 0, 'id');
header("Location: /");
exit;
  • This is your registration code. That's not even half of it. Where is your login code? Do you have anything in place to handle failed login attempts? – jmucchiello Jun 24 '09 at 16:26

6 Answers6

6

You didn't salt the password.

Also, md5() is considered not strong enough for hashing passwords.

Use hash('sha256', $password) instead.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • 2
    To add to this, read this about salt to understand why it's so important: http://stackoverflow.com/questions/420843/need-some-help-understanding-password-salt – cgp Jun 24 '09 at 16:21
3

I assume you're serving this on https, though you don't mention whether you do -- if you're not, username and password travel on the open net in the clear, and that's definitely not very safe.

There's a race condition -- you check whether the username is taken, first, and only later do you insert it. You should use a transaction, a least, and ideally just try to insert (with the uniqueness constraint imposed by the database) and catch the error in case of duplicates. And, you should do this only after all other sanity checks, i.e. when you've convinced yourself that, apart from possible duplicates, the registration attempt is OK.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • True enough about the race condition, but unless it's financials or dealing with potential identity theft data, it's probably overkill to do HTTPS. – cgp Jun 24 '09 at 16:23
3

Little bobby tables will give you a lot of headaches since you do not check for username validity.

z -
  • 7,130
  • 3
  • 40
  • 68
  • He does escape it, but yeah, there ought to be a sanity check as he might not escape it somewhere else etc... – cgp Jun 24 '09 at 16:24
  • that is true, he may have done it there, but since there's no mention of it anywhere its always safer to be redundant if security is a concern. – z - Jun 24 '09 at 16:26
3

You need to salt the password.

This is placed in the wrong place. Move it up a few lines before the $_POST vars are used.

// remove eventuakl space
foreach($_POST as $key => $val) $_POST[$key] = trim($val);

You are escaping the password fields for no reason. They are not being sent to the database. md5($password) is going to the database and it is not escaped.

EDIT: On the login side, you should be trimming anything you are trim on the registrations side.

jmucchiello
  • 18,754
  • 7
  • 41
  • 61
  • Also it would be faster/easier to use array_map for this instead of a loop. $_POST = array_map('trim', $_POST); – TJ L Jun 24 '09 at 16:55
  • @tj111, I agree it would be easier, but is it actually faster? (Just curious) – Josh Jun 24 '09 at 19:54
  • Probably not. He's only using 3 fields from the $_POST array. Also, he shouldn't trim the password fields. Why not let someone's password start or end with a space? – jmucchiello Jun 24 '09 at 21:18
1

Your error checking is out of order. I'd do the error checking in this order:

  1. Check for empty fields
  2. Check that the fields have valid values (filtering input)
  3. Escape the fields with mysql_real_escape_string before using the fields in SQL
  4. Check for the user in the SQL table

If you find an error don't continue with further checks. Guard each error check similar to your guard on the final INSERT statement.

You have no edits on the password fields besides using mysql_real_escape_string?

You should do mysql_connect before using mysql_real_escape_string. mysql_real_escape_string will use the connection to determine the character set of the connection. The character set will identify which characters to escape.

Paul Morgan
  • 31,226
  • 3
  • 24
  • 27
0

You should use parameters instead of building your sql dynamically. It will help prevent sql injection attacks. Little bobby tables will get you.

Jay
  • 13,803
  • 4
  • 42
  • 69
  • He's using PHP's ext/mysql API, which doesn't support SQL parameters. And he does use mysql_real_escape_string() which is an effective alternative when used correctly. – Bill Karwin Jun 24 '09 at 16:27
  • Bummer. I avoid escaping if possible. It adds complexity, reduces responsiveness, and can have bugs/vulnerabilities. By a slight design change I eliminate most of those things. Doesn't support parameters? yuk. – Jay Jun 26 '09 at 18:41