Following the best practices for sessions I have extended SessionHandler and regenerate the session ID at random (1 in 10 chance). The sessions work fine and I've started to implement some AJAX calls. However there are some very random cases where the session will be invalidated and the user logged out, I do not know the best and safest solution to this.
Let me walk you through what happens:
Let's say the user "loads more data" and initiates an AJAX call.
Browser sends headers including COOKIE, session ID is: klqlk0rldcasbos2f4li1db
The session handler validates this, it's fine, but we've hit the 1 in 10 chance so we session_regenerate_id
Server sends back new session ID: sbos2f4li1dbklqlk0rldca
During the process of sending data back, the user clicks to another page while the AJAX is still running (small chance, but it can happen).
The new session ID doesn't seem to be stored successfully by the browser. Seemingly if the request is cancelled all data is discarded, including the new session ID. (Not a browser bug, right?).
So then since the user has gone to a new page the client is sending a session ID klqlk0rldcasbos2f4li1db NOT the new one sbos2f4li1dbklqlk0rldca. Thus invalid session = logout.
I can also reliably reproduce this simply bashing F5 on a page where it doesn't get time to be completely received.
Possible Solutions?
When we session_regenerate_id we store the old ID somewhere, add
some sort of checking.
We disable session_regenerate_id during any
AJAX calls. Slight reduction in security and not really a solution.
Any input would be greatly appreciated.
UPDATE
I had been using session_regenerate_id(true) and setting this back to false (default) solves the issue, but now I'm wondering how good this is at preventing session hijacking if the old session stays on the system? Defeats the point of generating a new ID.
Related
I have a session formed the following way:
function sec_session_start() {
$session_name = 'primary_session';
$secure = false;
$httponly = true;
if (ini_set('session.use_only_cookies', 1) === FALSE) {
header("Location: /error?e=1");
exit();
}
$cookieParams = session_get_cookie_params();
session_set_cookie_params(3600,$cookieParams["path"],$cookieParams["domain"],$secure,$httponly);
session_name($session_name);
session_start();
session_regenerate_id(true);
}
I use this on all my page by adding
sec_session_start(); on my index page, which requires correct files depending on what page I am accessing.
It works perfectly fine with slow navigation.
However, when rapid navigational clicks occur, for some reason it unchecked, and the user is logged out.
How come?
This is the button I press rapidly. NOTE: It also changes the page from www.example.com to www.example.com/users, and then just repeats www.example.com/users until session is broken.
And this is the result after about, 2-3 rapid clicks. Works fine when pressed 1-2 times a second, max.
I have tried not using it as a function, and putting it on the absolutt TOP of the page without success.
The error seems to be session_regenerate(true).
This command generates a new session id. The parameter will delete the old session file if it is set to true. In this code it is set to true, so the session is created an started and then directly closed and deleted.
I think it appears only a few times because the command is called after session_start() was called and the output already started.
Try changing the parameter to false.
For the right use of session_regenerate() look into this question.
You appear to be discarding the old session ID on every page load. This is unnecessary, inefficient, and causes breakage.
If you navigate twice in quick succession what can happen here is:
the first request hits the server and starts executing your PHP, causing session_regenerate_id(true) to destroy the old session
this will cause the response from the PHP script to include a Set-Cookie: sessionid=something header, asking the browser to update its cookie to point to the new session
but the browser hasn't received the response from the script quite yet
you click a second time
the browser discards the existing request. Any Set-Cookie header isn't going to get listened to now
the browser makes a new request to your server. The browser doesn't know anything about a new session, so it includes the old session cookie
your script sees the old session cookie, which points to nothing, so user has to start a new session which isn't logged in
If you have an anti-Cross-Site-Request-Forgery system based on storing a synchroniser token in the session, then regenerating the session ID on every page load will also make any forms you use inoperable when the browser has multiple tabs open on the site at once, or when the user navigates with the Back button.
You should only session_regenerate_id when the authentication associated with a session is changed (primarily, when the user logs in).
Changing session IDs does not prevent session fixation; it is only a mitigation for when session fixation has already occurred through some other means (eg a vulnerable app on a neighbour subdomain injects a cookie into the shared parent domain).
If you didn't change the session ID, then an attacker who had already gained session fixation could get a full session hijack, by giving you a session ID she already generated and knows, and letting you log in using that session, upgrading it to an authenticated session. When you change the session ID on auth boundaries that isn't possible; the worst she can do now is push you into a session in which you are unexpectedly logged in as her. Which isn't ideal, but generally this constitutes a much less damaging attack.
For my login system, I have a token value that changes every time authentication occurs. Authentication occurs every time any page is accessed (by looking for token cookies and sessions ad such), as well as every $.ajax call (my thought is that I would want to ensure the user is authenticated at all times, and if ever authentication fails with a bad token or series or whatever, the system would automatically completely log out). During the authentication process, when it is determined that the current session is valid, a new token is generated, and that token is set as a cookie as well as updated in a MySQL table, as such:
$newtoken = hash("sha256", mt_rand());
my_mysqli_query($link,
'UPDATE _rememberme SET token = "'.$newtoken.'", lastupdated = "'.now().'"
WHERE series = "'.$series.'" AND email = "'.$email.'"');
setmycookie("token", $newtoken, 7);
When I rapidly refresh the browser, it ends up that the MySQL token and the cookie token do not match. I think that the problem is that during a rapid refresh, the MySQL table gets updated, but then a refresh occurs and the script aborts before updating the cookie. This causes future authentication failures because the cookie token doesn't match the MySQL token.
I would really appreciate some ideas on how to survive a user rapidly refreshing their browser.
I have researched this issue and had little success in finding a solution.
Your solution adds nothing to the security, and adds a great source of headache to your users.
If you use PHP Sessions, you don't have to rely on multiple cookies, and don't have to hash everything on every access. Having sessions in place will increase the security, as your users will not be able to change their session variables. A cookie is user-modifiable, so you can't blindly trust them. A user can change the PHPSESSION cookie, though, but the chances of changing the session to another one is so, so small that is almost impossible.
With your current code, if a user opens a link on a new tab, and before the request returns he opens another link, he will be logged off. The cookie will change on the first request, but before the browser gets the new value, the user submits another request with the old cookie. The new value is on the database, the new request with the old value is processed, and the session is invalidated. Another unhappy user confused because he was randomly logged off.
I have built a session library, and I am having a very random bug (I don't really know how to unit test this, so I just filled everything with log messages and waited till it happened again) that translates into a user being logged out, due to a session ID mismatch.
The flow of the application goes like this:
A request with a valid session ID is made
Session data is found for that session ID in the DB
The 'last activity' happens to be old, so it is regenerated and updated in the DB
The new session ID is sent in the response (as a cookie)
This works fine almost always, but sometimes the next request fails to match the session ID, because (this is my guess) it was sent after we updated the database (in the previous request, which was still running), but before the response with the new cookie came in.
Did I misunderstand the concept of regenerating a session ID? I'm regenerating session ID's only for security reasons, so someone that chose to be logged in for a year, still has his session ID changed from time to time.
One option would be to keep multiple session ids per user, but put expiry times on them - when it's time to regenerate a session id, add the new one, and put an expiry time on the old one equal to some reasonable period of time (a minute, perhaps). Keep accepting the old one in addition to the new one until the old one expires.
I assume you're using session_set_save_handler(), right..? If so, try doing the following:
session_regenerate_id($delete_old_session = true);
session_write_close();
Or even:
session_regenerate_id($delete_old_session = false);
session_write_close();
Calling session_write_close() should effectively save the new session data. You only have to pay attention when you call this (usually before privilege changes > redirects), since it ends the session.
End the current session and store
session data.
Session data is usually stored after
your script terminated without the
need to call session_write_close(),
but as session data is locked to
prevent concurrent writes only one
script may operate on a session at any
time.
After reading a great post on PHP session security .I have two questions from the discussion.
1)$_SERVER['HTTP_USER_AGENT'] -This gets the information about the user's browser and other details and since a person can access their account from a different computer then how is it useful?
2)session_regenerate_id - This regenerates a session id , How should I use it ? Is the session_id deleted after a session has timeout or closed?
Thanks for all your help.I appreciate each view and response.
$_SERVER['HTTP_USER_AGENT'] , you can use this information for when you are using special features that may not work for everyone, or if they want to get an idea of their target audience. This also is important when using the get_browser() function for finding out more information about the browser's capabilities. By having this information the user can be directed to a version of your site best suited to their browser.
session_regenerate_id, When it renames the session id it does not remove the old session, leaving it active and potentially usable by a would be hacker. This does not pose a problem if the function is only used during new session create as the means of preventing session fixation, which is the intended use btw. However, it makes it completely useless if used on each session based request to prevent session leakage via HTTP_REFERER and similar, since the previous session id is still usable. It also means that changing the id on “actions” as some scripts to do prevent session theft also is pointless; in fact it doubles the amount of session ids for the same user making it only simpler to assume their identity. Furthermore it means that on every call to the function there is duplication in the number of sessions entries that will hang around until they are considered expired and removed by the garbage collection process.
The User-Agent is useful for determining the browser being used, which may lead to guessing some of its capabilities. For example, most mobile devices can be accurately identified by their browser's user agent (see WURFL), thus allowing a site's developer to direct mobile devices to the site's mobile version.
However, it can be modified by the user, so its value should be taken with a grain of salt as is the case with any user input.
session_regenerate_id() doesn't delete the session. It merely changes its id to a newly created one. To avoid having its old session file hang around until auto deletion by the system, you can delete it yourself by setting the optional function parameter to true. Its use is to avoid session fixation attacks where an attacker can gain access to an existing session's data by knowing and presenting its id to the server.
1) Sessions aren't bound to accounts, they're bound to browser sessions. You can use the user agent information to see if that someone other user agent is trying to hijack the session. However it's not fail-proof. You can also use things like the user's IP address (or a given range of it) to catch hijack attempts.
2) By calling session_regenerate_id from time to time, you reduce the chance of someone hijacking the session. This is especially true if the session ID is passed in the URL. For example let's say someone accidentally pasted a link to a chat with the SID in the URL. If you regenerate the session ID periodically, the users who saw that link can't hijack the session with it, as the ID would've changed already.
I'll try to answer your questions from the bottom up: session_regenerate_id() is useful in preventing session fixation attacks, where a malicious user who has obtained your session ID hijacks your session and can then act as you. When you regenerate the session, you can track the latest session ID in a database or something similar, and only allow access with the most current session ID (incidentally, if you regenerate sessid frequently enough, this will prevent users from browsing your site with multiple browsers/windows), otherwise old sessions will be available by default (unless you pass a boolean true parameter to the session_regenerate_id function call).
Some security-crazy people will suggest regenerating the session ID after every request, but you can also track a session variable that increments per request, and just regenerate every X number of requests (5 or 10 or whatever you determine is a sufficient amount for your security level). The other option is to regenerate the session ID during a privileges escalation, such as logging in.
As for HTTP_USER_AGENT, it is mostly useful in implementing browser/client-specific functionality (for example, displaying a "Get Chrome!" link when users visit your site using Firefox or IE).
I'm using login function in my site with session.
This session of mine gets expired after a few minutes irrespective of whether the user has logged out or not.
Now what I want is that the session should only get expired when a user logs out. If a user doesn't log out his account and then comes back after 2-3 days, even then he should appear logged in.
I have found some examples where they have increased the time for a session to expire but I want that it should only expire on the log out event by the user irrespective of the time he took to log out.
How can I do that?
In particular, is this the right way to do so?
session_cache_expire(0);
session_start();
A solution that is often used, in this situation, is to:
have a not-too-long session duration: it will expire if the user is not active (that's just the way it works -- and that's better for your server if you have lots of users)
when user logs in, you set a cookie that contains what is needed for him to be recognized
if he comes back on the site (with the cookie, and without having an active session), you use the informations contained in that cookie to auto-log him in, re-creating the session at the same time.
This way:
you don't have thousands of sessions "active" with no good reason
you keep the standard way sessions work
And you have the advantage of "never being logged out", at least from the user's point of view.
Also note that with "normal" sessions, the cookie containing the session id will be deleted when the user closes his browser -- so, he will be disconnected, no matter how long the session's lifetime is.
With the solution I propose, you are the one who sets up how long the cookie should remain on the user's computer ;-)
It means, though, that when a user manually logs-out, you have to delete both his session and the cookie, of course -- so he's not immediatly re-auto-logged-in.
Of course, you have to be careful about what you set in the cookie: a cookie is not quite secure, so don't store a password in it, for instance ;-)
Actually, this way of doing things is how the "remember me" feature often works; except, here, your users will not have to check a checkbox to activate "remember me" ;-)
If you don't have the time to develop that kind of stuff, a pretty quick and dirty way is to use some Ajax request on all your pages, that will just "ping" a PHP page on the server -- this will keep the session active (but it's not quite a good way of doing things: you'll still have LOTS of sessions on the server, you'll have lots of useless requests... and it will only work as long as the user doesn't close his browser).
You can't do that with the PHP internal session handling alone. PHP will always send out the session id in a session-cookie which will expire when the user closes his browser. To achieve some sort of auto-login you'll need some accompanying code that sets a longer-lasting cookie on the user's browser and handles the recognition of these cookies and the mapping between the cookies value and the respective user account.
Please note that this greatly affects security issues so you'll have to take care of a lot of things. Please read the following on how a possible auto-login feature could be working:
Persistent Login Cookie Best Practice
Improved Persistent Login Cookie Best Practice
Do you remove your cookies while testing? Are cookies enabled? Do you destory the session somewhere in your code?
Also, see my answer to another post: Quick question about sessions in PHP which explains how to stay signed in. Just don't do a cronjob/sheduled task if you want the user to stay logged in forever.