13

Let's say there's a site/system with a logged in member area, and users are rarely, but very inconveniently logged out while working with the site/system.

It's doubtfully session expiring, since the user was not idle for very long. And even if they were idle, I added a periodic AJAX request, a so called heartbeat, which updates the sessions' access time, and modified time. I even added a touch($session_file) every time a user clicks something or a heartbeat is called. I tried regenerating session ID as well. Nothing helped.

And unfortunately, so far, I was not able to reproduce the problem locally, because it happens every so often, when there's more requests. Some php.ini parameters:

session.use_cookies = 1
session.use_only_cookies = 1
session.cookie_lifetime = 0
session.gc_probability = 1
session.gc_divisor = 1500
session.gc_maxlifetime = 10800
Keval Domadia
  • 4,768
  • 1
  • 37
  • 64
donk
  • 1,540
  • 4
  • 23
  • 46
  • Is it possible that some of your code does not include `session_start` in it and that's what truncates the session? – Peon Aug 13 '12 at 08:05
  • No, it's not possible. Everything is inherited from one super class/controller, which handles sessions and authentication. – donk Aug 13 '12 at 08:06
  • You can try adding log for session time changes and see at what point it expired. – Peon Aug 13 '12 at 08:08
  • Are you using Ubuntu? If yes, could you execute /usr/lib/php5/maxlifetime and tell us if it corresponds to what you expect (should be 3)? There is a cron which uses this setting : /etc/cron.d/php5 – greg0ire Aug 13 '12 at 08:15
  • It doesn't seem that session was expired, since the garbage collection could not collect it, since it was too recently used. I'm using CentOS 6.3 – donk Aug 13 '12 at 08:19
  • Make sure you are not initializing the session more than once (you have to call session_start exactly once). Next to session_start() add `if(defined("DEBUG_SESSION_START")) throw new Exception("session_start() must be called exactly once"); define("DEBUG_SESSION_START", true);` – oxygen Aug 21 '12 at 15:11

5 Answers5

6

Since sessions and authentication is already handled via one super controller in your code, it should be easy to at least rule out session destruction.

Typically only the login page creates a session, so at this point you can (and should) add a known value inside, such as the session id.

The other pages (including your heartbeat) resume an existing session, so at this point you look for the above value; if it's missing, you can do a few more checks:

  • was a session cookie passed? if not, browser / cookie issue.
  • does the session cookie correspond with session_id()? if not, session file was lost due to garbage collection.
  • does the known value exist in the session? if not, session was truncated or someone is trying to do session adoption attack.
  • does the known value correspond to the session cookie? if not, the session was established via different means than cookie; you could check session.use_only_cookies setting.

The above set of checks should point you in the right direction.

Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • I managed to catch a session ID and cookie PHPSESSID mismatch. But I was not bounced to the login page, I was kept on. Weird. Any ideas? – donk Aug 13 '12 at 11:07
  • @donkapone in most cases, if there's a mismatch and the `$_SESSION` is completely empty the issue points towards your super controller code. if the session is not empty, things are really messed up because that means your session ids are overlapping =O – Ja͢ck Aug 13 '12 at 11:14
  • That's the messed up bit, because $_SESSION is not empty, but $_SESSION['session_id'] and $_COOKIE['PHPSESSID'] are not equal. What the hell is happening? – donk Aug 13 '12 at 11:30
  • @donkapone in that case the session was loaded by a different means other than cookies – Ja͢ck Aug 14 '12 at 22:48
3

I presume you are using built in PHP file session storage? There are known race conditions problems with it.

I had similar issues with loosing session id's when there were concurrent requests from same session id. Since file was locked by first request all other concurrent connections were unable to access file and some of them generated new session id. Those situations were also very rare and it took me time to locate the problem. Since then I'm using memcached for session storage and those problems vanished.

Miro
  • 456
  • 2
  • 10
  • Yes, I'm using file session storage. What if I would use mysql database for session storage? – donk Aug 15 '12 at 10:26
2

It is hard for me to answer this without further information however are you sure this only happens when you have more traffic. What can be done:

  • Add the codez
  • OS distro and version
  • PHP version

Do you have any session checks that rely on the IP. If so it might be that you are actually using a proxy which gives a different IP once every so oftern (like Cloudflare) in which case you can use the HTTP_CF_CONNECTING_IP to get the actually constant IP.

I have also noticed one more thing:

session.use_only_cookies = 1
session.cookie_lifetime = 0

So the cookie will actually be destroyed when the browser closes. Is it possible the connection is lost because the user closes the tab (which in some browsers does actually clear the cookies)?

As others have suggested I would strongely say you move to a DB session. Using a central session store can help in stopping race conditions, it won't stop them completely but it does make it harder. Also if you really get that many session ids you might wanna look into using something other than the built in PHP session id, however the built in hash is extremely unique and has a very low chance of being duplicate so I don't think that's your problem.

Clarification Edit:

If you are using a different proxy to Cloudflare then you will, of course, need to get the HTTP header thart your proxy sets, using Cloudflare is an example.

Sammaye
  • 43,242
  • 7
  • 104
  • 146
0

Here's something I've used. It uses javascript to 'ping' the server on an interval of 10 minutes.

<form method="POST" target="keepalive-target" id="keepalive-form">
    <input type="hidden" name="keepalive-count" id="keepalive-count">
</form>
<iframe style="display:none; width:0px; height:0px;" name="keepalive-target"></iframe>
<script>
    var kaCount=0;
    setInterval(function()
    {
        document.getElementById("keepalive-count").value=kaCount++
        document.getElementById("keepalive-form").submit();
    },600000);
</script>
Jon Hulka
  • 1,259
  • 10
  • 15
0

All the provided answers have shown good insight to the question, but I just have to share the solution to my exact problem. I was finally able to reproduce the problem and fix it.

So the system contained two subsystems, let's say admin and client interfaces. The admin was unexpectedly logged out when they logged in as client in another tab and logged out the client interface while being logged in as admin. It was doing this because everything was written to one session with namespaces. What I did is remove the code that kept destroying the session on logout action, and replaced it with session namespace unsetting and replacing with guest session for that namespace that only has access to the login page.

donk
  • 1,540
  • 4
  • 23
  • 46