PHP & MySQL site login, security concepts

Discussions of secure PHP coding. Security in software is important, so don't be afraid to ask. And when answering: be anal. Nitpick. No security vulnerability is too small.

Moderator: General Moderators

User avatar
batfastad
Forum Contributor
Posts: 433
Joined: Tue Mar 30, 2004 4:24 am
Location: London, UK

Re: PHP & MySQL site login, security concepts

Post by batfastad »

Confused now 8O
I'm storing the salt and password hash in separate DB fields

Here's my code for updating the password hash and salt once a user logs in

Code: Select all

    // LOGIN SUCCESS, RE-HASH PASSWORD AND NEW SALT
    $salt = hash('sha256', uniqid( mt_rand(), true));
    $pass = hash('sha256', $_POST['pass']).$salt.$pepper;
 
    // SQL QUERY TO UPDATE STORED HASHED PASS AND SALT IN DB
    $sql_query = "UPDATE `users` SET `pass`='$pass', `salt`='$salt' WHERE `user_id`='$user_id'";
    $sql_result = mysql_query($sql_query);
    $sql_affected_rows = mysql_affected_rows();
    $sql_error_msg = mysql_error();
    $sql_error_code = mysql_errno();
So my comparison

Code: Select all

if ( hash('sha256', $_POST['pass']).$sql_data['salt'].$pepper === $sql_data['pass'].$sql_data['salt'].$pepper)
I think I'm majorly messed up and confused here
Do I append the salt and pepper to the password, then hash the whole lot?
Or hash the password, then add s&p to the end of the password hash?

Sorry :(
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: PHP & MySQL site login, security concepts

Post by VladSun »

No, that's not the way a password hash is salted or peppered.
Take a look at some articles about securing password hashes - so you could understand why you need salt&pepper.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
kaisellgren
DevNet Resident
Posts: 1675
Joined: Sat Jan 07, 2006 5:52 am
Location: Lahti, Finland.

Re: PHP & MySQL site login, security concepts

Post by kaisellgren »

Code: Select all

PasswordHash = Hash ( Password + Salt + Filesystem Key)
When you are regenerating the session identifier, PHP will send the setcookie header for the client to update her cookies.
User avatar
batfastad
Forum Contributor
Posts: 433
Joined: Tue Mar 30, 2004 4:24 am
Location: London, UK

Re: PHP & MySQL site login, security concepts

Post by batfastad »

Ok fantastic... Hopefully got it

Code: Select all

if ( hash('sha256', $_POST['pass'].$sql_data['salt'].$pepper) === $sql_data['pass']) {
 
    // LOGIN SUCCESS, NEW SALT AND RE-HASH PASSWORD
    $salt = hash('sha256', uniqid( mt_rand(), true));
    $pass = hash('sha256', $_POST['pass'].$salt.$pepper);
Some final questions...

1) I'll completely ignore all that manual $_COOKIE stuff mentioned in that code snippet.
But after reading through the man page for session_regenerate_id I came across this comment... http://www.php.net/manual/en/function.s ... .php#87905
Are the issues covered in that function something I need to be aware of?

2) If I wanted to implement an auto-login feature, obviously I don't store the username/password anywhere. If the user has their browser set to remember username/password then that's their problem and there's not much I can do about it (dynamic input names stored in a session perhaps)
So how does it work?
Do I store the user's session ID in a separate cookie. Then when the user returns, compare their IP address with a session_id/IP pair stored in the database?

Thanks so much for all your help so far guys :lol:
I really want to try and get all this right first time out
User avatar
kaisellgren
DevNet Resident
Posts: 1675
Joined: Sat Jan 07, 2006 5:52 am
Location: Lahti, Finland.

Re: PHP & MySQL site login, security concepts

Post by kaisellgren »

The code makes more sense now.
batfastad wrote:Are the issues covered in that function something I need to be aware of?
If you are using PHP 5.1+, then set the parameter to true to remove the old session data.
batfastad wrote:2) If I wanted to implement an auto-login feature, obviously I don't store the username/password anywhere. If the user has their browser set to remember username/password then that's their problem and there's not much I can do about it (dynamic input names stored in a session perhaps)
So how does it work?
Do I store the user's session ID in a separate cookie. Then when the user returns, compare their IP address with a session_id/IP pair stored in the database?
All you need for a simple implementation is a single cookie with the session identifier contained. Whenever they access your site, the browser will give the session identifier and the user gets in. You can try replacing the submit button of your login form with a custom button that uses JavaScript to read the input fields and submits them manually. That way most web browsers won't save passwords.
User avatar
batfastad
Forum Contributor
Posts: 433
Joined: Tue Mar 30, 2004 4:24 am
Location: London, UK

Re: PHP & MySQL site login, security concepts

Post by batfastad »

kaisellgren wrote:All you need for a simple implementation is a single cookie with the session identifier contained. Whenever they access your site, the browser will give the session identifier and the user gets in.
This cookie, are you talking about the one that PHP sets using session_start? Or do I create a second cookie manually to store the session id?
Are there any session settings I need to change from the default? Session cookie expiration time or anything?

Should I store session id/IP address pairs of currently logged-in sessions in a database? Then compare the IP address of that session with their current IP?

Kai: I saw this thread you posted with some excellent info... viewtopic.php?p=504722#p504722
You suggest a way of creating a stronger session identifier

Code: Select all

if (($handle = @fopen('/dev/urandom','rb'))) {
    $rand = fread($handle,8); // 8 is just an example that we might want to use, type 64 for to get 512 bits strong random
    fclose($handle);
}
hash('sha512',uniqid($rand.$_SERVER['REMOTE_ADDR'].implode('',fstat(__FILE__,'r')).memory_get_usage(),true));
Is that something I should look into using instead of PHPs built-in algorithm?

How about changing these settings in php.ini per this thread: viewtopic.php?p=520259#p520259

Code: Select all

session.entropy_file = /dev/urandom
session.entropy_length = 128
This system I'm working on will be a sbuscription database of data specific to our industry. It will be running on a completely separate Linux VPS to the rest of our websites so I will have complete control of the configuration.

Thanks for all your help :lol:
User avatar
kaisellgren
DevNet Resident
Posts: 1675
Joined: Sat Jan 07, 2006 5:52 am
Location: Lahti, Finland.

Re: PHP & MySQL site login, security concepts

Post by kaisellgren »

batfastad wrote:This cookie, are you talking about the one that PHP sets using session_start? Or do I create a second cookie manually to store the session id?
PHP. You don't need to create your own cookies. And web browsers only deal with one cookie file (at least so far) with different value and key pairs.
batfastad wrote:Are there any session settings I need to change from the default? Session cookie expiration time or anything?
Are you talking about all the settings provided by the Session extension. http://fi2.php.net/manual/en/session.configuration.php
batfastad wrote:Should I store session id/IP address pairs of currently logged-in sessions in a database? Then compare the IP address of that session with their current IP?
Depends on your needs. Ask yourself whether you want to allow people with dynamic IPs to stay logged in (if your project even has a user base). If that's a problem, you could partly tie IPs to sessions. Like, don't allow the IPv4 value to change more than 2^10, for instance.
batfastad wrote:You suggest a way of creating a stronger session identifier
For anything about random in security, you should (must) use cryptographically strong random number generators. Urandom provided by Unix -like operating systems is one way to produce sufficiently random data. PHP 5.3 has http://fi2.php.net/manual/en/function.o ... -bytes.php
batfastad wrote:How about changing these settings in php.ini per this thread: viewtopic.php?p=520259#p520259

Code: Select all

session.entropy_file = /dev/urandom
session.entropy_length = 128
That would certainly make the session stronger.

session.cookie_secure, session.cookie_httponly are also worth mentioning. The "secure cookie" is only useful if you run your site through HTTPS, but the HTTP-only mode is a definitive option to turn on if possible. Also, having session.hash_function set as "sha256" in PHP 5.3 is a good idea (and "1" on < 5.3).
User avatar
batfastad
Forum Contributor
Posts: 433
Joined: Tue Mar 30, 2004 4:24 am
Location: London, UK

Re: PHP & MySQL site login, security concepts

Post by batfastad »

Hi Kai

Many thanks for all your help on this!
Having had a dig through the php.ini session options, I now know why my sessions were expiring when the browser was closed ;)
That's the default behaviour and the reason I thought I had to create my own session ID cookie

Final question...

So if I change this in my php.ini

Code: Select all

session.entropy_file = /dev/urandom
session.entropy_length = 128
Do I also need to do this to generate strong session IDs?

Code: Select all

if (($handle = @fopen('/dev/urandom', 'rb'))) {
    $rand = fread($handle, 64); // 8 is just an example that we might want to use, type 64 for to get 512 bits strong random
    fclose($handle);
}
echo hash('sha512', uniqid($rand.$_SERVER['REMOTE_ADDR'].implode('', fstat( fopen(__FILE__, 'r'))).memory_get_usage(), true));
Or does that php.ini setting basically mean my session id is already random enough that I don't need to worry about generating my own one with the above PHP code?

Thanks, Ben
User avatar
kaisellgren
DevNet Resident
Posts: 1675
Joined: Sat Jan 07, 2006 5:52 am
Location: Lahti, Finland.

Re: PHP & MySQL site login, security concepts

Post by kaisellgren »

batfastad wrote:Do I also need to do this to generate strong session IDs?
Since you are using PHP's session engine, it will generate the session identifier for you. If you were to create your own session system, then you would need to create strong session identifiers by yourself, which could be achieved with this simple code (for Unix):

Code: Select all

if (($handle = @fopen('/dev/urandom', 'rb'))) {
     $rand = fread($handle, 64);
     fclose($handle);
 }
echo
I would say that random number generator is cryptographically suitable for any PHP application.
User avatar
batfastad
Forum Contributor
Posts: 433
Joined: Tue Mar 30, 2004 4:24 am
Location: London, UK

Re: PHP & MySQL site login, security concepts

Post by batfastad »

Hi Kai
Thanks for all the help so far.
So they're both doing the same thing... improving the randomness of the session ID by using urandom
I think I've got the password, salting and session security pretty much down now.
I'm just thinking about the structure and order of my login routine and global auth script that will be running on each secure page.

On every page of my site I'm going to be running this code to check whether a user is logged in:

Code: Select all

if($_SESSION['auth'] !== 1) {
    // NOT LOGGED IN. REDIRECT TO LOGIN PAGE, SAVING REQUESTED SCRIPT
} else {
    // LOGGED IN
 
    // SQL QUERY TO GET USER DATA
    $sql_query = "SELECT `ip`, `expire_stamp`, `session_id`, `user_id` FROM `users` WHERE `session_id`='$session_id'";
    // EXECUTE QUERY, GET ARRAY OF SQL DATA
 
    // COMPARE USER'S IP WITH THAT STORED IN THE DB AT LAST LOGIN
}
Hopefully final questions...

1) The only way someone could spoof a login is by trying to send a fake session ID of a logged in user (of which there's many many combinations thanks to urandom). Is that correct?

2) For my auto-login feature I'll need to be storing my session ID in my users DB table, then overwriting it every time the user logs in and the session regenerated.
Is my query above secure - searching my users table for a session ID which matches the user's from the cookie?
Or is there a better way I should structure the above login check?

3) Should I also store another random hash in a manual cookie and in my database?
That way someone trying to brute force session IDs not only has to get the session ID of a logged-in user but also the user's matching secret manual cookie hash I made.

Thanks so much for all the help. I really want to get this clear in my mind before I start plunging in to building this.

Thanks, Ben
User avatar
kaisellgren
DevNet Resident
Posts: 1675
Joined: Sat Jan 07, 2006 5:52 am
Location: Lahti, Finland.

Re: PHP & MySQL site login, security concepts

Post by kaisellgren »

batfastad wrote:1) The only way someone could spoof a login is by trying to send a fake session ID of a logged in user (of which there's many many combinations thanks to urandom). Is that correct?
That is Session Forging, and yes, the stronger the identifier the harder to success. If you create 32-bytes of good random, you have 2^32 different possibilities. Beware of applying hashes, they limit you to certain range. You can also use lossless encoding (e.g. Base64, pack the binary data to hex, ...), but you can use those only for strong random generators, otherwise, you would be disclosing information about your random generator. Also, you should stop brute forcing on the identifiers by blocking IPs that have tried too many identifiers.
batfastad wrote:2) For my auto-login feature I'll need to be storing my session ID in my users DB table, then overwriting it every time the user logs in and the session regenerated.
Is my query above secure - searching my users table for a session ID which matches the user's from the cookie?
Or is there a better way I should structure the above login check?
You can store the IP, expiration and anything else on the session. When the session is regenerated, you wouldn't need to change anything.
batfastad wrote:3) Should I also store another random hash in a manual cookie and in my database?
That way someone trying to brute force session IDs not only has to get the session ID of a logged-in user but also the user's matching secret manual cookie hash I made.
Did you notice that this does not actually differ from having a stronger session identifier? :P
User avatar
batfastad
Forum Contributor
Posts: 433
Joined: Tue Mar 30, 2004 4:24 am
Location: London, UK

Re: PHP & MySQL site login, security concepts

Post by batfastad »

Hi Kai

How do I block IPs that have tried too many session identifiers?
Store ip, session id and a timestamp in a separate DB table, then every page request check if that IP has done too many in a given time period?

I'm planning on installing the mod_evasive Apache module as well to stop DOS attack but I guess that's a slightly different attack.

By storing the session ID and IP in my users DB table, it means we can limit to only one session per user id.
But for other uses, then storing the IP in the session would be a better idea.
Since this will be a subscription-based contact info database, this is something we want to do to stop people trying to harvest our data out. We will also have limits on the number of searches per day/week/month/year that users can do and other stuff.

I should clarify that the expiry_stamp column in the above SQL query is nothing to do with the session, but the date the user's account subscription ends.

If the IP address changes but the session ID is the same, then I'll send the user to the login screen but with the username pre-entered. So auto-login only works from the same IP.

But a simple check on $_SESSION['auth'] === 1 is all that's needed to check a user is logged in then?!

Great stuff, thanks for all your help everyone. Hopefully this info will be of use to others and I'll let you know how it all goes
Thanks so much, Ben
User avatar
kaisellgren
DevNet Resident
Posts: 1675
Joined: Sat Jan 07, 2006 5:52 am
Location: Lahti, Finland.

Re: PHP & MySQL site login, security concepts

Post by kaisellgren »

batfastad wrote:How do I block IPs that have tried too many session identifiers?
The logic is understood only by PHP, so, use a database and check if certain IP has tried too many different session identifiers. You can make a separate table, store IPs that have tried to forge session identifiers (and how many times). You can be clever and store the last checked identifier in the database, and then compare if it has changed (he tried a different identifier), then increase a counter. So, you don't need to add rows for each identifier. And if the session existed, don't increase counters / add new entries as she's a legitimate user (hopefully :P).
batfastad wrote:I'm planning on installing the mod_evasive Apache module as well to stop DOS attack but I guess that's a slightly different attack.
Yeah, mod_evasive is for spam flood.
batfastad wrote:If the IP address changes but the session ID is the same, then I'll send the user to the login screen but with the username pre-entered. So auto-login only works from the same IP.
My IP changes quite often. Don't do strict IP checks. Do loose IP checks. Right now we still use IPv4, so, you can use ip2long() to convert it to a number and from there do the loose IP check (e.g. the IP can change up to 255 numbers up or down).
batfastad wrote:But a simple check on $_SESSION['auth'] === 1 is all that's needed to check a user is logged in then?!
That's ok for your purpose I guess. It does not tell where she is authenticated to, or what she is authorized to do, but that does not matter here as you have only one place to log in?

In a software I'm writing, I store a permission ID in the session after a successful logon. Then I use $_SESSION['permissionId'] (to be precise, I use my own session system though) to query the database to fetch the permissions.
User avatar
batfastad
Forum Contributor
Posts: 433
Joined: Tue Mar 30, 2004 4:24 am
Location: London, UK

Re: PHP & MySQL site login, security concepts

Post by batfastad »

kaisellgren wrote:The logic is understood only by PHP, so, use a database and check if certain IP has tried too many different session identifiers. You can make a separate table, store IPs that have tried to forge session identifiers (and how many times). You can be clever and store the last checked identifier in the database, and then compare if it has changed (he tried a different identifier), then increase a counter. So, you don't need to add rows for each identifier. And if the session existed, don't increase counters / add new entries as she's a legitimate user (hopefully :P).
Yeah that's a better idea, will reduce the amount of records needing to be stored in the DB.
kaisellgren wrote:My IP changes quite often. Don't do strict IP checks. Do loose IP checks. Right now we still use IPv4, so, you can use ip2long() to convert it to a number and from there do the loose IP check (e.g. the IP can change up to 255 numbers up or down).
That's excellent, I'll definitely do that.
kaisellgren wrote:That's ok for your purpose I guess. It does not tell where she is authenticated to, or what she is authorized to do, but that does not matter here as you have only one place to log in?
Yeah there'll only be 1 level of permission. Any admin stuff is taken care of in our main DB tables on a different server. Then every 24 hours we'll export selected records and insert them into the subscription tables.

Ok I think I'm pretty much sorted on all this stuff now... got to write the whole thing now :lol:

Thanks so much for all your help! :D
Hope others find this thread useful as well
User avatar
batfastad
Forum Contributor
Posts: 433
Joined: Tue Mar 30, 2004 4:24 am
Location: London, UK

Re: PHP & MySQL site login, security concepts

Post by batfastad »

Hi Kai
You thought I was finished... not quite :wink:

This is how I create my new salt and password and store it in my DB:

Code: Select all

$salt = hash('sha256', uniqid( mt_rand(), true));
$pass = hash('sha256', $_POST['pass'].$salt.$pepper);
But how should I generate my pepper?
Using the same method as the salt, above?

Or should I do the pepper using this mega-random function you mentioned above and over in other threads?

Code: Select all

if (($handle = @fopen('/dev/urandom','rb'))) {
    $rand = fread($handle, 64); // 8 is just an example that we might want to use, type 64 for to get 512 bits strong random
    fclose($handle);
}
$handle = fopen(__FILE__, 'r');
echo hash('sha512', uniqid($rand.$_SERVER['REMOTE_ADDR'].implode('', fstat($handle)).memory_get_usage(), true));
fclose($handle);
Cheers, B
Post Reply