Scenario:
After a user has logged in, a session variable is set confirming their login.
At the top of every page, login session variable is confirmed valid
If it's not, they're booted out.
No persistent cookies are used, only session
Question:
Is this a strong enough security measure by itself, or should I
Set two session variables to validate eachother and/or
Implement database/hash validation
...?
========
(Incidentally, while I was researching this question, this wiki is a fantastic read.)
It is enough to store just user login (or user id) in the session.
To prevent session fixation/hijacking everything you need is just to implement simple algorythm (pseudocode):
if (!isset($_SESSION['hash']) {
$_SESSION['hash'] = md5(!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no ua');
} else if ($_SESSION['hash'] != md5(!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no ua')) {
session_regenerate_id();
$_SESSION = array();
$_SESSION['hash'] = md5(!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no ua');
}
You could move the hash calculation into some function to prevent of duplication, i've just shown a sketch of possible protection.
This is how I implemented this kind of protection in my kohana session class:
abstract class Session extends Kohana_Session
{
public function read($id = null)
{
parent::read($id);
$hash = $this->calculateHash();
$sessionHash = $this->get('session_fixation');
if (!$sessionHash) {
$this->set('session_fixation', $hash);
} elseif ($sessionHash != $hash) {
$this->regenerate();
$_SESSION = array();
$this->set('session_fixation', $hash);
}
}
private function calculateHash()
{
$ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
$ua = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'no ua';
$charset = !empty($_SERVER['HTTP_ACCEPT_CHARSET']) ? $_SERVER['HTTP_ACCEPT_CHARSET'] : 'no charset';
$ip = substr($ip, 0, strrpos($ip, '.') - 1);
return md5($ua . $ip . $charset);
}
}
Don't try to write your own session scheme, PHP will do it better.
yes you can add more information to your $_SESSION to help prevent session hijacking
for example I generate a fingerprint by combining a secret phrase or random data with the user agent and the session_id() and hash it all. To hijack a session the user would need to figure out a valid session_id, and the hash of the fingerprint. it will look like this. This is a good read
$_SESSION['fingerprint'] = md5('somethingSecret' . $_SERVER['HTTP_USER_AGENT']. session_id());
then you would validate the session like
$check_print = md5('somethingSecret' . $_SERVER['HTTP_USER_AGENT']. session_id());
if($check_print != $_SESSION['fingerprint'] || $_SESSION['authenticated']){
//invalid session
}
As of 15 November, the two answers I have received do not address my question, which was
"Is this [a single session variable] a strong enough security measure by itself?"
This question says yes, but there seems to be some dissension. Here is a summary of the various results:
1) A single session variable is not enough security since a session can be hijacked fairly easily.
2) Since this can occur, no session is truly safe, but it can be made safer with the addition of a fingerprint. This ensures a unique, repeat-able check each time a session needs validation. #zerkms recommends a hash of User-Agent and a few others (refer to his code).
3) Salting the fingerprint is mostly useless since it obscures the data but is replicated on every client machine, therefore losing its unique-ness.
4) A database solution is useless since it is a client-side problem.
Not the definitive answer I was looking for, but I suppose it will have to do, for lack of anything better.
Reading that has helped/confused me further:
Session hijacking and PHP
Is HTTPS the only defense against Session Hijacking in an open network?
There is nothing you can do, except use HTTPS.
It doesn't matter how many cookies you add or what data you hash; it can all be sniffed and sent back to the server.
If you're going to force a user to use a single UA throughout the life of their request, that can help: you don't need any special hashing business, because you're hashing it into $_SESSION which neither the user nor the hijacker can access directly, so why bother hashing it? Might as well just store $_SESSION["reportedUA"] = $_SERVER["HTTP_USER_AGENT"] on log-in and then check reportedUA on each request.
That, too, is trivial to hijack, once you realise it's happening, as you need only sniff the reported UA when you sniff the session cookie, and start using that.
What next? IP address? Session hijacking might be happening from behind a NAT, in which case you're screwed. Your users might be using dial-up, in which case they're screwed.
This problem has no solution: there is no way. There couldn't be a way. If a hacker can see your session cookies, then they can mess you up, because there's no additional information or challenge related to something only the user knows (i.e. password) that's sent with each request.
The only way to make the session secure is to secure the entire session.
Is this a strong enough security measure by itself,
Set two session variables to validate eachother and/or
Implement database/hash validation
No, and the reason is this: Anything that your valid user can send to your server for authentication (Session ID, cookies, some hashed string, anything!) can be sniffed by others if it's not encrypted. Even if the server processes the data with md5 hashing, salt, double-session-variable checks, id or whatever, and stores that information, it is easily reproduced by the server when it receives the spoofed data again from some other source.
As many people have suggested, SSL is the only way to prevent this type of evesdropping.
It has occurred to me that, were the server to generate a new session id for each request, and allow the browser to reply with it only once, there could theoretically be only one hijacker request or post before the server and the authorized browser knew about it. Still unacceptable, though, 'cause one is enough to do serious damage.
Hey what about this:
Create a single-use GUID and random salt and encrypt it with a shared password using PHP - this is sent as the session id or a cookie.
The client receives the cookie, decrypts it with the shared password using javascript (there are many enc/dec utilities available)
Set the current cookie or session id to the GUID.
That would ensure that nobody could hijack the session unless they knew the password, which is never sent over the network.
SSL seems much easier, and is more secure still.
EDIT: Ok, it's been done - nevermind ;-)
Related
If a user successfully logs in and passes security checks (username, password, 2fa... whatever) and the hypothetical PHP login system then does something like:
session_start();
$_SESSION['logged_in_userid'] = 1;
How safe is it to then rely on the existence of that $_SESSION value 'logged_in_userid' as proof that this person really did actually previously pass the full security check? Not very, I'm thinking.
If by XSS someone was to determine the PHPSESSID, and manually add that to their own local cookies. Are they not then going to be automatically assumed to be logged in when they visit the same system?
If so - would a resolution be to record, at login time, also in the _SESSION the: REMOTE_ADDR, HTTP_USER_AGENT and X_FORWARDED_FOR and compare at each request time not only the _SESSION logged_in_userid but also the _SESSION REMOTE_ADDR etc etc with those found in the current _SERVER vars?
Or is that flawed also?
It actually is secure but you need to do some extra stuff to make sure that someone can't 'hijack' the session by XSS for example as you mentioned:
static protected function preventHijacking()
{
if(!isset($_SESSION['IPaddress']) || !isset($_SESSION['userAgent']))
return false;
if ($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR'])
return false;
if( $_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT'])
return false;
return true;
}
The function above checks the IP address to see if it's with the original user and not by someone else.
OWASP has a great guideline how to handle session management (and authentication). In practice you'll have to store a session ID at the client and verify the session every request:
https://www.owasp.org/index.php/Session_Management_Cheat_Sheet
What you are talking about is also covered in the: session fixation
I am attempting to set a cookie and then check to see if the cookie has been set.
So in one function, I have it make the cookies:
public function makeCookies(){
Cookie::queue('logged_in', $value, 15);
Cookie::queue('user_id', 2);
//return Response::make()->withCookie(Cookie::make('logged_in', $value, 15))->withCookie(Cookie::forever('user_id', 2));
}
And in the other function, I try to check to see if the cookie has been set:
public function checkCookies(){
$this->makeCookies();
if(Cookie::get('logged_in') && Cookie::get('user_id')){
return 'Logged In!';
}
}
However the only way this works is if I add 'return' before $this->makeCookies(); However, I want to be able to get to the conditional below it. Is there any way I can go about doing this? Any help is greatly appreciated.
To understand the Cookie Creation/Read process:
The user's browser sends a request for a page, along with any cookies that it currently has for the site
The site serves up the page, and any cookies you create become a header in your response.
Subsequent requests to your site will send the cookies created in #2.
What you are asking...to be able to read cookies that you create in step #2 in step #1...not possible.
Now, depending on how the Cookie class is created, you could make it so that when the Cookie::queue() is called, that it creates in-memory data that reflects what the cookie "should be" on the next request, but it doesn't truly know whether or not the user's browser will accept cookies, etc.
This is why many sites, after creating a cookie give the user a redirect to a page with something like ?checkCookie=1. This way, on the subsequent request, they can verify that your browser supports cookies...and if the cookie doesn't exist on the ?checkCookie page, they give you an error saying that their site requires cookie support. However, it does require a second round to the server to read cookies from the browser that were created.
UPDATE 2015-04-24 Per #Scopey, Laravel does support in-memory retrieval of cookies via queued(). So, you should be able to do:
public function checkCookies(){
$this->makeCookies();
$loggedIn = Cookie::get('logged_in') ?: Cookie::queued('logged_in');
$userId = Cookie::get('user_id') ?: Cookie::queued('user_id');
if( $loggedIn && $userId ){
return 'Logged In!';
}
}
SECURITY CONCERNS (NOT DIRECTLY ANSWERING THE QUESTION)
Your question was only about the cookies, so that's all I answered. However, now that I'm looking at your code, I feel I would be remiss not to point this out for anyone that happens to be reading this. This may just be a "how to" for yourself and not production code, but that code could be very dangerous if it ever went public.
Make sure you do NOT TRUST a user_id stored in a cookie to determine what user is coming in via cookies. If you rely on that, and I come to your site, I can modify my cookie to any user_id I want and get into other people's accounts.
General Safety Rules:
A cookie should contain a GUID, or similar random string to identify the session. This random string should be sufficiently long (e.g. 32 characters or greater, IMHO) that it is not easy for someone to brute-force their way to hijacking sessions.
The user_id should be stored in the $_SESSION (or laravel's wrapper for session if applicable) so that the user doesn't have any access to the user_id to be able to modify it.
In plain PHP, this something like this for the login page:
session_start();
if( isValidPassword($_POST['username'], $_POST['password']) ) {
$_SESSION['user_id'] = $user->Id;
}
else {
die('invalid login credentials');
}
The session_start() method automatically generates a cookie for the user with that long, random string (so you don't even have to worry about that part.)
On subsequent pages, you just check the session user_id to know who is logged in:
session_start();
if( empty($_SESSION['user_id']) ) {
die('You are not logged in and cannot access this page');
}
Change as needed per Laravel's documentation, which if they have their own session wrapper, I'm sure is well documented on best practices.
Excellent description by #KevinNelson about cookies but Laravel does support fetching back any cookies you have queued in the current request. Try using
Cookie::queued('logged_in');
The catch is, the cookie will only be "queued" during the request that you queued it. You will have to use get like you are for any other requests.
I need to know how secure is my user authentication code that I am using in my php applications.
This is my login check function
// Is Login
//*********************************************************************************
public function isLogin()
{
$validation = new Validation();
if(!$validation->isEmpty($_SESSION["AdminId"]) && !$validation->isEmpty($_SESSION["AdminUsername"]) && !$validation->isEmpty($_SESSION["AdminName"]))
{
return true;
}
else
{
return false;
}
}
I have a authenticate file which i call from top of every user account's page which is as under
if (!$admin->isLogin())
{
header("Location: index.php?type=warning&msg=" .urlencode(ADMIN_INVALID_LOGIN));
exit();
}
The session values for example Adminusername is the actual username of the admin, adminname is the alphabetical name of the admin and adminid is the record id from mysql table such as $_SESSION["Adminusername"] = administrator though i am storing this value after encypting it.
I need to know is this a secure method to just store the values and check for them or I need to have some kind of advance functionality to make it more secure such as salt or time check etc.
I would appreciate your suggestions and feedbacks. If possible, your authenticate code / class.
Thanks in advance.
Amardeep Singh
use session regenerate id to get a new ID in every request, so u can prevent session hijacking .. read this manual : http://php.net/manual/en/function.session-regenerate-id.php
I am storing this value after encypting it
I don't understand... Why do you crypt your AdministratorName?
As you surely know, the user cannot manipulate his session as he wants, because the session is on the serverSide and your code decide what to write into session-data.
I think, salting or timechecking do not raise your security-level.
Because HTTP is stateless, each session is identified by a id, which ist mostly saved in a cookie on the client side. Each of your request to this server contains this SID, because it's the only way your server could identify a visitor.
If you use HTTP-Transport, your data (end also your SID) is sent through the internet without encryption. So a hacker could read your SessionID and take over your Session (which contains logged in User-Data). To prevent this, you can force HTTPS connection for logged in users.
If you have the possibility to switch all your pages to https-only, do it. If you must switch between http and https (for example https only if user is loggedin) it becomes really difficult to guarante security!
I have a classifieds website which I am creating a login system for...
In the code below, a form with "username" and "password" has been submitted to. Also a "remember_me" feature is available (Code is not tested yet):
else if($row['password']===$pass){
session_start();
$_SESSION['logged_in'] = '1';
$remember_me = isset($_POST['remember']) ? $_POST['remember'] : '0';
if($remember_me=='1'){
$text = "SECRET_TEXT_AND_NUMBERS_HERE";
$username= $row['username'];
$salt1 = sha1($row['alt_username']);
$salt2 = sha1($text);
$cookie_value = $salt1.':'.$username.':'.sha1($row['alt_username'].$salt2.$salt1);
setcookie("s_b", $cookie_value, time()+60*60*24*100, "/");
}
}
Now, is this code a good start for a login page?
Also, an important follow-up question to all this, if users want to stay logged in, do I then set a $_SESSION variable like the one in the code, and just check if that is set in the beginning of all pages on the site?
if(isset($_SESSION['logged_in'])) // Then user is logged in already
or do I check to see if the cookie created in the login page is set instead of checking the session?
logging in is about security; security is always more difficult then it seems.
There are a couple of things that you could improve in your code. first:
the security for your password is in the strength of the hasing algorithm. You choose to use sha1 (better than md5, but could be improved by using sha256 or bCrypt if you use PHP version >= 5.3)
First
The salt you use is supposed to be a random value, stored alongside the hashed result.
in other words, the value to store in your database is:
$salt = [some random string of predifend lenght]; // Let's say 16 characters in length
$storedValue = $salt . sha256($salt . $password);
you check the password:
if ($row['username'] == $_POST['username'] && substr($row['$storedValue'], 16) == sha256(substr($row['$storedValue'], 0, 16) . $_POST['password'])) {
// login ok
} else {
// login fail
}
(better yet)
Use a proven library for the password hashing stuff, take a look at: Portable PHP password hashing framework and try to use the CRYPT_BLOWFISH algorithm if at all popssible.
Second
You should only store the session key in the session cookie. all other information is stored on the server.
The session cookie is already send out by PHP's session_start() function call, so you do not have to worry about this anymore.
if you want to check the sessions lifetime, you should store this information in the session array:
$_SESSION['lastActivity'] = time()+60*60*24*100;
Third
The remember me token is a 'password equivalent' so you should only store a hash of the token in your database, just treat it as a password, only this 'password' is not typed by the user, but read from the cookie.
The whole point of a hash is that its non-reversible, so it's not really adding any value the way you've used for the remember me function. Stop pretending it does anything useful, and use a random token for the remember me (and log this in the database against the username) then, if you get a client presenting a remember me cookie without an authenticated session, you know where to look to find out who it is.
(this also allows a sensible approach to be applied where the user keeps moving to different machines - you might say keep the last 2 values - and flag when they try to remember me from a 3rd machine).
A 100 day timeout is rather long - maybe 30 days (with an automatic refresh might be more appropriate depending on the level of risk.
I have a session that I gave to users that has matching password = stored password, like a simple login system :
// Checks Password and Username
if ($pSys->checkPassword($AccountData['password'], $StoredData['password'])) {
$_SESSION['login'] = true;
}
The question is: is this secure enough?
// put this on every header page that needs to be loggedin.
function loginCheck(){
if ( empty( $_SESSION['login'] )) {
header( 'location:index.php' );
die();
}
}
Is there a difference between die() and exit()? Second, some say that I should add session_regenerate_id()? (Is that an overkill?) Anyway the real question is said above.
addon*
I have read PHP Session Security but it seems it doesn't match my problem here (that link is just to general).
Here is the checkPassword() method
function checkPassword($password, $storedpassword) {
if($password == $storedpassword){
return true;
}
}
Answering the first part: empty and die are not comparable:
empty is to check if a variable does not exists or has a value equal to false (see also this type comparison table).
die is an alias of exit and is used to immediately abort the execution of the current script with an optional message.
Now to your authentication example: Yes, you should use session_regenerate_id to generate a new session ID and revoke the old session ID by setting the optional parameter for session_regenerate_id to true:
if (!sizeof($ErrorAccount)) { // Checks Password and Username
session_regenerate_id(true);
$_SESSION['login'] = true;
}
The purpose of session_regenerate_id is to avoid session fixation attacks. This will not be necessary if the server only allows session ids to be sent via cookies, but since PHP by default allows them in URL, you're strongly recommended to regenerate the id.
Since you are looking for answers about security, also don't keep the stored password in plain text. At the very least, salt and hash your passwords, then store the hash. Rehash and compare hashes, not plain text.
You could added a token (hash) to the form and then validate the token to make sure that the token which was submitted via the form is still valid. This helps to prevent CSRF attacks.
You could also store the IP address and browser together with the token in a database for additional validation, however you need to be aware that some ISP's change the clients IP address fairly often and could cause the validation to fail incorrectly.
More Info