views:

189

answers:

6

I'm developing my own PHP framework. It seems all the security articles I have read use vastly different methods for user authentication than I do so I could use some help in finding security holes.

Some information that might be useful before I start. I use mod_rewrite for my MVC url's. Passwords are encrypted with 24 character salt unique to each user. mysql_real_escape_string and/or variable typecasting on everything going in, and htmlspecialchars on everything coming out.

Step-by step process:

Top of every page:

session_start();
session_regenerate_id();

If user logs in via login form, generate new random token to put in user's MySQL row. Hash is generated based on user's salt (from when they first registered) and the new token. Store the hash and plaintext username in session variables, and duplicate in cookies if 'Remember me' is checked.

On every page, check for cookies. If cookies set, copy their values into session variables. Then compare $_SESSION['name'] and $_SESSION['hash'] against MySQL database. Destroy all cookies and session variables if they don't match so they have to log in again.

If login is valid, some of the user's information from the MySQL database is stored in an array for easy access. So far, I've assumed that this array is clean so when limiting user access I refer to user.rank and deny access if it's below what's required for that page.

I've tried to test all the common attacks like XSS and CSRF, but maybe I'm just not good enough at hacking my own site! My system seems way too simple for it to actually be secure (the security code is only 100 lines long). What am I missing?

Edit: For calling functions from the controller, anything that uses anything other than SELECT queries will require $_POST data to confirm a delete, for example, in addition to the user rank requirements.

I've also spent alot of time searching for the vulnerabilities with mysql_real_escape string but I haven't found any information that is up-to-date (everything is from several years ago at least and has apparently been fixed). All I know is that the problem was something to do with encoding. If that problem still exists today, how can I avoid it?

The encrypt function I borrowed from somewhere and modified:

public function encrypt($str, $salt = NULL) {
    if ($salt == NULL) $salt = substr(md5(uniqid(rand(), true)), 0, 24);
    else $salt = substr($salt, 0, 24);
    return $salt.sha1($salt.$str);
}
A: 

with session_regenerate_id(); you'll make the "back" button in your browser unusable. But still combined with "remember me" feature it become useless.
I can't see the CSRF defence in your description
There is not a single problem with mysql_real_escape string

Col. Shrapnel
I just tested the "Back" button on IE, Firefox, and Chrome and it worked fine. Remember me works also even after closing the browser.About the CSRF, the way I structured it is so that functions are called like http://example.com/profile/edit or http://example.com/article/delete. Am I safe from CSRF since all these functions require a minimum user rank for the page to even show? And I don't believe a user can be tricked into deleting their own articles by clicking on a malicious link like http://example.com/article/delete/623 because they have to confirm with a $_POST form.
Lotus Notes
How will the back button be unusable? Either the browser won't reload the page, so it's the same, or it will, also sending the new session cookie. The only way it would break that I can think of is when passing the session ID as an URL parameter, which is ugly anyway.
Bart van Heukelom
@Bart Oh my bad. I was thinking of pasing via URL and it is ugly indeed.
Col. Shrapnel
@byronh Yes well your confirmation post would be faked instead :) The only way to defend from CSRF is to use unique tokens placed in the form
Col. Shrapnel
A: 

You may want to check the user.rank against a whitelist, to make sure that the value exists in the lookup table and is valid (active, not deleted etc), and also to force typecasting and range.

As for tokens, you will want to generate a new token for each page that expects to receive GET/POST data from a form. If a session is hijacked then a token for the session could be insufficient. Setting a shorter timeout for the session could mitigate.

Don't trust anything from a cookie right away, consider using http://php.net/manual/en/book.filter.php before stashing in the Session.

If you plan to have an admin panel for the system it's ideal to enforce password strength, manual admin login name selection (in preference to admin/password stock logins upon setup).

Blacklist the usual login names such as "a';DROP DATABASE..." and "Administrator", you know the drill.

Use least-privilege accounts when setting up your database, you don't want your visitors to be playing havoc with your tables.

mattoc
Is it even possible for an attack to be done just through the existence of session variables? I filter them before actually using them in any database functions. Also, mysql_query() can only carry out one database action so it's impossible to perform additional delete or drop actions, but rather only getting more query rows than they should be able to access (if they somehow got past mysql_real_escape_string).
Lotus Notes
A: 

"Passwords are sha1 and md5 encrypted"

Why would there be a need to use two hash functions? I guess you do it due to a lack of understanding (no offense!). You should read this article on secure password schemes (hint: use bcrypt).

middus
Sorry, maybe I wasn't specific enough but I edited the question with the encryption function I'm using. If I understand correctly it's not using sha1 and md5 at the same time, it's using it so that you can use the same function either with or without a salt?
Lotus Notes
+2  A: 

I don't see any real prevention for CSRF attacks in there. CSRF is (from my understanding) the most commonly overlooked/screwed up vulnerability in websites. Many of the big guys have been hacked via CSRF vulnerabilities.

The current best solution to this problem is using synchronizer tokens.

You had also better be very religious about escaping/encoding user input properly. Things that are inserted into the database must be escaped, and things output back to the user have to be encoded based on their context: CSS, HTML, JavaScript, etc all have to be encoded differently.

Furthermore, you shouldn't combine SHA-1 with MD5. While SHA-1 is stronger than MD5, you are essentially weakening it by combining it with MD5. Additionally, neither of these algorithms are recommended for use in new applications. For the time being you should be using SHA-256 (it is available out-of-the-box in PHP).

Carson Myers
+2  A: 

Do yourself a favour and use a standard library for hashing your passwords.

Because security tends to be a lot more complicated and with more invisible screw up possibilities than most programmers could tackle alone, using a standard library is almost always easiest and most secure (if not the only) available option.

The standard library:
Take a look at: Portable PHP password hashing framework: phpass and make sure you use the CRYPT_BLOWFISH algorithm if at all possible.

example of code using phpass (v0.2):

require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hashed stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass has been implemented in some quite well known projects:

  • phpBB3
  • WordPress 2.5+ as well as bbPress
  • the Drupal 7 release, (module available for Drupal 5 & 6)
  • others

The good thing is that you do not need to worry about the details, those details have been programmed by people with experience and reviewed by many folks on the internet.

Whatever you do if you go for the 'I'll do it myself, thank you' approach, do not use MD5 anymore. It is a nice hashing algorithm, but utterly broken for security purposes.

Currently, using crypt, with CRYPT_BLOWFISH is the best practice.
CRYPT_BLOWFISH in PHP is an implementation of the Bcrypt hash. Bcrypt is based on the Blowfish block cipher, making use of it's expensive key setup to slow the algorithm down.

For more information on password storage schemes, you could also read Jeff`s blog post about it: You're Probably Storing Passwords Incorrectly

Jacco
Thank you! I've replaced my password thing with that one. Now what would be a secure way to send an account validation key and uniquely identify a user with a confirmation email?
Lotus Notes
I think you best start a new question for that.
Jacco
+1  A: 

By trying to over-engineer the approach you may be undermining the security.

What session handler are you using? Have you checked that session_regenerate_id() renames an existing session rather than making a copy of it? If the latter then you are creating lots of session files that are much easier to find with a random search.

How do you deal with users splitting sessions? This line of code is going to break depending on the browser and how the user splits the session.

The only place you should be using session_regenerate_id() is when you authenticate the user and when the user logs out.

mysql_real_escape_string and/or variable typecasting on everything going in

NO, no, no. This keeps coming up here.

You don't change the representation of incoming data. By all means /validate/ input, and you should always /sanitize/ output - you don't /sanitize/ input.

If user logs in via login form, generate new random token to put in user's MySQL row. Hash is generated based on user's salt (from when they first registered) and the new token. Store the hash and plaintext username in session variables,

Nothing here which improves your security

Store the hash and plaintext username in session variables, and duplicate in cookies if 'Remember me' is checked

So you store the plaintext password on the users machine! This is so not secure. If you want a remember me function, then encrypt the password and an expiry date with a salt and store that.

On every page, check for cookies. If cookies set, copy their values into session variables.

So users can manipulate any value stored in the session by returning a cookie with the same name? OK so they probably can't predict the hash so it'd be tricky to fake the user - but there are lots of other ways this could be used to compromise your system.

You've tried very hard to make your system secure - but at best all you've actually done is obfuscated the parts of your system that work properly.

C.

symcbean
The password isn't stored in plaintext of course, only the username so I can know which user to check the hash against.
Lotus Notes