Sessions across subdomains - .domain.com PHPSESSID changes!

I have a site that I have moved from a Windows (WAMP) server to a Linux (LAMP) server. I am running PHP 5 on Apache 2.

The site is an online store in addition to an information site. The user adds items to their cart at www.domain.com, and upon clicking checkout, they are taken to the secure SSL site at secure.domain.com. The session cookie is carried over that holds the cart, because the session is set to be good for all subdomains - secure or not with this code:

ini_set(‘session.cookie_domain’, ‘.domain.com’);
session_start();

This same code is in both the regular and secure sites, included at the top of each page. This worked fine on the Windows server, but on the linux side, I now see that the PHPSESSID cookie for .domain.com is REPLACED ENTIRELY with a new session id.

When I go back from the secure side to the unsecure side, the session id is replaced AGAIN!

This is something I would never expect in a million years, and I’ve already put too much time into it to not ask someone else. Any ideas?

Do you have permission to change settings with ini_set() on the linux server? Check that the setting actually changed.

view the http headers your scripts are sending to make sure the cookie is being sent as you expect it.

the live http headers firefox extension is very useful here.

Took me a second to not believe it was an issue with ini_set and security. When I had commented out those lines, it sets a separate cookie for each subdomain as expected.

I totally forgot I had Live HTTP Headers installed. It didn’t tell me anything new, but I’m glad to have it around. Here’s its response

GET / HTTP/1.1 - Request to HTTP subdomain
[…]
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1
[…]
Cookie: PHPSESSID=Fwjt86AV2QuOTczIPFcgSrWWIk84Hy6VSRT3Hd7ZQVI59enHluhs6NTj_w09cv_s

HTTP/1.x 200 OK
Set-Cookie: PHPSESSID=iwO6I8jD0cn6VPHtVtNcky32qN1pVjgFW5OazWnTK8lvDCD0PkFEQPleeigOWaj4; path=/; domain=.domain.com; HttpOnly

GET /checkout/ HTTP/1.1 - Request to HTTPS subdomain
[…]
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1
[…]
Cookie: PHPSESSID=iwO6I8jD0cn6VPHtVtNcky32qN1pVjgFW5OazWnTK8lvDCD0PkFEQPleeigOWaj4

HTTP/1.x 200 OK
Set-Cookie: PHPSESSID=0_7OiDug-lXKcIbxGZOPYqOTAH1Yq51Pxau3Y_4KyvPjArOkq10dOJcofPJW06FZ; path=/; domain=.domain.com; HttpOnly

As can be plainly seen, the cookie is being completely ignored. I see that PHP, as a security precaution used HttpOnly - but aren’t all the modern browsers compatible with that? And wouldn’t that not matter as long as the sending header includes the cookie?

your browser is sending the cookie.

php just doesnt like the session id so its starting a new session and sending you back the new session id.

check your session.save_path to see if the session files are being created, use ini_get(). maybe you have 2 different paths depending on https or http

As clamcrusher says, it can seem like the session files are beeing created on two different paths.

Also, since you have provided no information regarding the domain and the sub domain. You are certain that they actually are hosted on the same server? If you use a webhost account instead of a virtual/dedicated server, the subdomains can be placed on completly different servers.

If the case is that there is different session paths, either due to different servers or due to the paths differ. Then the best solution would actually just be to add a session handler system, saving the sessions to a database.

The reason is simply, due to if you do that the session is accessable and usable on any server as long as they have access to the database the session is stored on. And best of all, you can setup the code for this in 10 min.

This is all on the same server. In fact, I set up the system from the ground up. It is running SuSE Linux 10 OSS. I can access all config files and make any changes needed.

Oddly, when I echo ini_get(‘session.save_path’), it outputs an empty string. Does this just mean it uses the default? Or could this be a permissions problem?

http://www.php.net/manual/en/ref.session.php#ini.session.save-path

check /tmp

I have manually set session.save_path to /tmp

no subdirectories, and I see the session files there.

let me get this straight.

you goto http domain. session is started and works, as you can browse around other pages on http domain and this session is maintained.

you goto httpS domain and your current sessions is not used, but another session is started. this session works properly as you can browser around httpS domain and this session is maintained.

when you return again to the http domain, yet neither the origional http session nor the httpS session is used, and yet a 3rd session is started?

I’ve done some more looking, and I’m completely certain it’s because secure.domain.com and www.domain.com are on different virtual hosts. I’m still not aware of a PHP setting that can cause this. www.domain.com and domain.com on the same virtual host share the cookie just fine.

does php run as a different user between these different vhosts? suexec or suphp are some examples which would support this.

theres a chance that php cannot read the session file due to file permissions, if the user is changing.

im assuming you do have error_reporting set to E_ALL and display_errors is turned on?

and please, do not ignore any of my posts/questions if you want me to continue helping you.

I’m assuming that php runs as the same user, as all the files in /tmp are owned by wwwrun:www.

I was typing my last comment while you were posting yours. I’m not ignoring any questions just yet. Yes, the http/httpS is how you described. I can browse around one side or the other and virtual host is maintained. When I go back and forth between the two, they overwrite each other’s session cookie. Each visit back and forth I get a brand new Session ID. If I go from http to https and then back to http, there is a total of 3 unique session ID’s generated.

I have errors set to E_ALL, but display_errors is turned off. I have access to the error log.

is it possible that the problem is in your application? at this point id switch to using a mock up to debug.

eg run this from both domains


<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

session_start();
echo "<pre>\
";
var_dump(@++$_SESSION['i']);
print_r($_COOKIE);
var_dump(session_id());
var_dump(session_name());


foreach (ini_get_all() as $k => $v)
    if (strstr($k, 'sess')) {
        echo "\
\
$k:";
        var_dump($v);
    }
?>

Using the debug script, I get this on the https side:


int(1)
Array
(
    [PHPSESSID] => ihm5pgsr7il4ersmju61lu7tf1opruq6
)
string(32) "ihm5pgsr7il4ersmju61lu7tf1opruq6"
string(9) "PHPSESSID"


session.auto_start:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.bug_compat_42:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.bug_compat_warn:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.cache_expire:array(3) {
  ["global_value"]=>
  string(3) "180"
  ["local_value"]=>
  string(3) "180"
  ["access"]=>
  int(7)
}


session.cache_limiter:array(3) {
  ["global_value"]=>
  string(7) "nocache"
  ["local_value"]=>
  string(7) "nocache"
  ["access"]=>
  int(7)
}


session.cookie_domain:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


session.cookie_httponly:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.cookie_lifetime:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.cookie_path:array(3) {
  ["global_value"]=>
  string(1) "/"
  ["local_value"]=>
  string(1) "/"
  ["access"]=>
  int(7)
}


session.cookie_secure:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


session.entropy_file:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


session.entropy_length:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.gc_divisor:array(3) {
  ["global_value"]=>
  string(4) "1000"
  ["local_value"]=>
  string(4) "1000"
  ["access"]=>
  int(7)
}


session.gc_maxlifetime:array(3) {
  ["global_value"]=>
  string(4) "1440"
  ["local_value"]=>
  string(4) "1440"
  ["access"]=>
  int(7)
}


session.gc_probability:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.hash_bits_per_character:array(3) {
  ["global_value"]=>
  string(1) "5"
  ["local_value"]=>
  string(1) "5"
  ["access"]=>
  int(7)
}


session.hash_function:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.name:array(3) {
  ["global_value"]=>
  string(9) "PHPSESSID"
  ["local_value"]=>
  string(9) "PHPSESSID"
  ["access"]=>
  int(7)
}


session.referer_check:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


session.save_handler:array(3) {
  ["global_value"]=>
  string(5) "files"
  ["local_value"]=>
  string(5) "files"
  ["access"]=>
  int(7)
}


session.save_path:array(3) {
  ["global_value"]=>
  string(4) "/tmp"
  ["local_value"]=>
  string(4) "/tmp"
  ["access"]=>
  int(7)
}


session.serialize_handler:array(3) {
  ["global_value"]=>
  string(3) "php"
  ["local_value"]=>
  string(3) "php"
  ["access"]=>
  int(7)
}


session.use_cookies:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.use_only_cookies:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.use_trans_sid:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


suhosin.session.checkraddr:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(6)
}


suhosin.session.cryptdocroot:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(6)
}


suhosin.session.cryptkey:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


suhosin.session.cryptraddr:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(6)
}


suhosin.session.cryptua:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(6)
}


suhosin.session.encrypt:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(6)
}


suhosin.session.max_id_length:array(3) {
  ["global_value"]=>
  string(3) "128"
  ["local_value"]=>
  string(3) "128"
  ["access"]=>
  int(6)
}



and this for the HTTP side:



int(1)
Array
(
    [PHPSESSID] =>
)
string(32) "nsj6mv2ejv2tv96r6hifhcgr9nchn8br"
string(9) "PHPSESSID"


session.auto_start:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.bug_compat_42:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.bug_compat_warn:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.cache_expire:array(3) {
  ["global_value"]=>
  string(3) "180"
  ["local_value"]=>
  string(3) "180"
  ["access"]=>
  int(7)
}


session.cache_limiter:array(3) {
  ["global_value"]=>
  string(7) "nocache"
  ["local_value"]=>
  string(7) "nocache"
  ["access"]=>
  int(7)
}


session.cookie_domain:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


session.cookie_httponly:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.cookie_lifetime:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.cookie_path:array(3) {
  ["global_value"]=>
  string(1) "/"
  ["local_value"]=>
  string(1) "/"
  ["access"]=>
  int(7)
}


session.cookie_secure:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


session.entropy_file:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


session.entropy_length:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


session.gc_divisor:array(3) {
  ["global_value"]=>
  string(4) "1000"
  ["local_value"]=>
  string(4) "1000"
  ["access"]=>
  int(7)
}


session.gc_maxlifetime:array(3) {
  ["global_value"]=>
  string(4) "1440"
  ["local_value"]=>
  string(4) "1440"
  ["access"]=>
  int(7)
}


session.gc_probability:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.hash_bits_per_character:array(3) {
  ["global_value"]=>
  string(1) "5"
  ["local_value"]=>
  string(1) "5"
  ["access"]=>
  int(7)
}


session.hash_function:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.name:array(3) {
  ["global_value"]=>
  string(9) "PHPSESSID"
  ["local_value"]=>
  string(9) "PHPSESSID"
  ["access"]=>
  int(7)
}


session.referer_check:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


session.save_handler:array(3) {
  ["global_value"]=>
  string(5) "files"
  ["local_value"]=>
  string(5) "files"
  ["access"]=>
  int(7)
}


session.save_path:array(3) {
  ["global_value"]=>
  string(4) "/tmp"
  ["local_value"]=>
  string(4) "/tmp"
  ["access"]=>
  int(7)
}


session.serialize_handler:array(3) {
  ["global_value"]=>
  string(3) "php"
  ["local_value"]=>
  string(3) "php"
  ["access"]=>
  int(7)
}


session.use_cookies:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.use_only_cookies:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(7)
}


session.use_trans_sid:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(7)
}


suhosin.session.checkraddr:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(6)
}


suhosin.session.cryptdocroot:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(6)
}


suhosin.session.cryptkey:array(3) {
  ["global_value"]=>
  string(0) ""
  ["local_value"]=>
  string(0) ""
  ["access"]=>
  int(7)
}


suhosin.session.cryptraddr:array(3) {
  ["global_value"]=>
  string(1) "0"
  ["local_value"]=>
  string(1) "0"
  ["access"]=>
  int(6)
}


suhosin.session.cryptua:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(6)
}


suhosin.session.encrypt:array(3) {
  ["global_value"]=>
  string(1) "1"
  ["local_value"]=>
  string(1) "1"
  ["access"]=>
  int(6)
}


suhosin.session.max_id_length:array(3) {
  ["global_value"]=>
  string(3) "128"
  ["local_value"]=>
  string(3) "128"
  ["access"]=>
  int(6)
}



The only difference I see is in the first few lines, where PHPSESSID is repeated on the https side. I have also temporarily disabled all the suhosin options which do not affect the results in either the sample script or the real site.

ok thats odd. in the cookie array on http, you have a cookie for the session id, but the value is just plain empty. using the firefox ext to see the headers you send, is the browser sending the cookie with a value, when php is reporting the cookie empty?

can you find the cookie value if using apache_request_headers()?

something else too.
origionally your session ids were 64 chars long, and included the _ char sometimes, which is not in the range as defined in the manual, even when
session.hash_bits_per_character = 6
so im going to assume that suhosin(which im not familiar with) is doing some “magic” here considering the manual sais the files session handler doesnt support that character.

Well - Live HTTP Headers certainly thinks I’m sending the cookie.

I placed apache_request_headers at the top of your script.

Now that suhosin is disabled (which I’m not familliar with either), the php session id is hashed on the cookie, but once it gets into PHP it’s all letters & numbers. At least that’s my conclusion from the output:

http side:

array(10) {
  ["Host"]=>
  string(23) "www.otterbasketball.com"
  ["User-Agent"]=>
  string(92) "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1"
  ["Accept"]=>
  string(99) "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
  ["Accept-Language"]=>
  string(14) "en-us,en;q=0.5"
  ["Accept-Encoding"]=>
  string(12) "gzip,deflate"
  ["Accept-Charset"]=>
  string(30) "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
  ["Keep-Alive"]=>
  string(3) "300"
  ["Connection"]=>
  string(10) "keep-alive"
  ["Cookie"]=>
  string(74) "PHPSESSID=x7N3Q2jtqTv7fDrMJ9FKINoZsIry3M6cm3YKpaQVabBrOnc5DGYMd5-iaDEQhW5M"
  ["Cache-Control"]=>
  string(9) "max-age=0"
}
int(5)
Array
(
    [PHPSESSID] => pb2ncrcsdafgmu9j574m3eo50groql84
)
string(32) "pb2ncrcsdafgmu9j574m3eo50groql84"
string(9) "PHPSESSID"


https page

array(10) {
  ["Host"]=>
  string(26) "secure.otterbasketball.com"
  ["User-Agent"]=>
  string(92) "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1"
  ["Accept"]=>
  string(99) "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
  ["Accept-Language"]=>
  string(14) "en-us,en;q=0.5"
  ["Accept-Encoding"]=>
  string(12) "gzip,deflate"
  ["Accept-Charset"]=>
  string(30) "ISO-8859-1,utf-8;q=0.7,*;q=0.7"
  ["Keep-Alive"]=>
  string(3) "300"
  ["Connection"]=>
  string(10) "keep-alive"
  ["Cookie"]=>
  string(74) "PHPSESSID=SyJBXeLppya1VgkgAtyExt7G0i0GocAjCP4WNQh5TXgu-zlCFv3i0r36BP4lWCC9"
  ["Cache-Control"]=>
  string(9) "max-age=0"
}
int(5)
Array
(
    [PHPSESSID] => k26l5gfdjvk3p2lo32he3dp4h2keipfs
)
string(32) "k26l5gfdjvk3p2lo32he3dp4h2keipfs"
string(9) "PHPSESSID"


It appears that both pages are behaving correctly with separate cookies. I’m going to try these pages with .domain.com and see what happens.

Oh, and the reason for the cookie being empty on the earlier output was because it was the first load of the page. I got lost in your script somewhere around the @ and the incrementing counter and so I didn’t make the right assumption about your script.

When I ini_set(‘session.cookie_domain’,‘.domain.com’) on http and https, I find that the cookie is sent to Apache, but not being passed on to php, even though it is accepted by Apache. Either Apache or PHP is rejecting the cookie for that host. This happens going from http to https or from https to http.

This thread is getting long and off-topic. I think this is an Apache hashing function which isn’t matching me up. I don’t know for sure, of course.

In the session section of php.ini I added the following:

suhosin.session.cryptdocroot = Off
suhosin.cookie.cryptdocroot = Off

I had already set the session not to include the docroot in the encryption key, but apparently it gets encrypted anyway with the cookie encryption. With both of these settings in place, my sessions work just fine across virtual hosts.

I am using the default PHP5 installation on SuSE 10.3 OSS edition. Apparently this almost unheard-of hardening extension (not even mentioned much on the web) is pretty good - and we’d better get used to hearing about it.