This is a complement of PHP Sessions across sub domains
I tried what is indicated on that question, and I see that the issue wasn't given.
So I need to have sessions across sub-domains (www.example.com to forum.example.com)
What I did on www.example.com is
session_name("a_name");
session_set_cookie_params(0, '/', '.example.com');
session_start();
echo session_id();
$_SESSION['test'] = 123;
On forum.example.com
session_name("a_name");
session_set_cookie_params(0, '/', '.example.com');
session_start();
echo session_id();
print_r($_SESSION);
The session_id are exactly the same, but the $_SESSION doesn't output anything.
How to make forum.example.com output 123 ?
I tried session.cookie_domain = .example.com but doesn't change anything
When I go on forum.example.com it destroys the www.example.com sessions, and it does the same on the other way, like if it detects that it comes from another sub-domain and erases everything for security.
The 2 sub-domains are on the same Debian server
Another thing that I noticed is that without session_name and session_set_cookie_params it still has exactly the same session_id, when I set session.cookie_domain
Thank You
Ok, I've thought about this for a while and I think I've got it.
First things first: since you are getting the same session id from both servers, we can rule out any cookie-related issues. Clearly, you are successfully creating a cookie named a_name (though I'd recommend only alphanumeric characters for that cookie name) on www.example.com, and successfully reading that a_name cookie on forum.example.com. But, like you said, you aren't getting any data from forum.example.com. The session.cookie_lifetime = 0 is not an issue: that just means that the session cookie remains until the browser is closed.
We should delve into PHP's session handling a bit further. The session id you are reading out with session_id() refers to a file on your server. Typically, that file is present in /tmp/sess_$session_id. The contents of that file are your $_SESSION array, serialized. (Keep in mind that the data is not serialized the same way that serialize() in PHP does... but that's not important right now.).
I think this is a file permission-related issue:
/tmp/sess_$session_id file is set with www.example.com's user and group.
forum.example.com attempts to open /tmp/sess_$session_id, but doesn't have the proper permissions.
As a result, you get an empty result when trying to print_r($_SESSION);
Solution:
Check your server's configuration file to make sure that www.example.com and forum.example.com are running as THE SAME USER AND GROUP. That is critical! For Apache, find your *.conf file:
User youruser
Group yourgroup
For nginx, find nginx.conf:
user youruser yourgroup;
If changing the server config files is not an option, then you should make sure that the users running the two sites are in the same group.
You can verify that this is the problem by first loading www.example.com and then sudo ls -ltc sess_* in your server's shell, via SSH (find the sess_ ending in your $session_id). Next, load forum.example.com and then sudo ls -ltc sess_* again, to see the user and/or group change.
For this answer I've made a few assumptions:
The user has to enter there credentials at least once on each domain (any other way would be a serious security issue)
You have access to either a database or file space outside the web root.
sub-domain, domain or any other name will be referenced as "site"
The aim is to have a common file (physical file or serialized in database) accessible from each site/domain.
For my example I will be using a database, since it's the idea I'm putting across, not database/file access techniques, I will have unnecessary lines removed, IE: How to connect to the database.
If this concept is what you were after, or if anyone else want me to fill in the blanks for completeness, just leave a comment. On with the code.
I would take a completely different approach.
From what I gather from your question, and the related post you linked to, you are trying to share a session using a common session name.
Each site has it's own session id.
Each site has it's own authentication cookie ( $_COOKIE['userid'] or $_COOKIE['userhash'] ).
Individual sessions are created, and a common cookie is stored on each site.
Using a custom session handler each site reads the same data. class MySessionHandler implements SessionHandlerInterface
My after thought was an even simpler approach, a class that acts like a session handler, reading / writing to a common file. Since php's session handler doesn't save the data until the script has ended.
Original idea - Won't go into details, it's just for reference.
class MySessionHandler implements SessionHandlerInterface {
private $savePath;
public function read($id) {
$id = some_user_authentication_function();
$hash = $_COOKIE['_h'];
$result = mysql_query("SELECT sess_data FROM login_table WHERE user_id = {$id} AND hash = {$hash}");
return $result['sess_data'];
}
public function write($id, $data) {
$id = some_user_authentication_function();
$hash = $_COOKIE['_h'];
$result = mysql_query("UPDATE login_table SET sess_data = {$data} WHERE user_id = {$id} AND hash = {$hash}");
return ($result === false) ? false : true;
}
}
$handler = new MySessionHandler();
session_set_save_handler($handler, true);
session_start();
class customSessionHandler
{
private $hash;
private $id;
private $sess_db;
public function __construct($db) {
$this->hash = $_COOKIE['hash'];
$this->id = some_user_authentication_function($this->hash);
$this->sess_db = $db;
}
public function get($key) {
$query =
"SELECT value ".
"FROM ".$this->sess_db.
"WHERE user_id = {$id} ".
" AND hash = {$hash} ".
" AND key = {$key}";
$result = mysql_query($query);
return $result['key'];
}
public function set($key, $val) {
$query =
"REPLACE INTO ".$this->sess_db.
"SET {$key} = {$val} ".
"WHERE user_id = {$id} ".
" AND hash = {$hash}";
return (mysql_query($query) === false) ? false : true;
}
}
$handler = new customSessionHandler('sess_data');
session_start();
As stated at the beginning, any code that isn't essential to explaining the concept has been removed.
Things that might not be obvious to everyone:
- $key and $val need to be sanitized before sending to the database. (prevent injection attacks)
- The hash gets sent to your login functions, so the hash can be used to clear the session data when needed, can also be used in the authentication of the user.
- mysql prepared statements would be ideal here, so you can prepare the two queries in the constructor, then you just reuse the statement on every call. Then put the connection close code in the destructor.
After thought
There would be much greater security if each site had it's own hash.
Then if you detect a security anomaly, you can just block or re-request the credentials from the one site, without compromising the hash for the network of sites.
To implement this would be as easy as setting up another table containing:
- user_id
- site_name (example.com)
- hash
- timeout
- re-authenticate
and modifying the session_data table, so instead of accessing the $key => $val pair by hash, you access it by user_id.
Thanks for reading, hopefully it will be of use to someone.
Related
i'm trying to a build a secure sessionID.
Usually i store the UserID as the $_SESSION['session'];, now im not too sure if it is really secure to have the userID as the sessionID. so what i have done is tested this theory although now i cannot access the users information and after i kill the session the sessions are still active and available?
CODE to check login if true or false:
if(password_verify($userPass, $user['userPasswd']))
{
session_start();
$UID = str_replace($user['text'].$user['text1'], '', $user['uniqID']);
$_SESSION['SESSION'] = sha1(md5(sha1($UID)));
return true;
} else {
return false;
}
Logout Script: EDIT fixed using $_SESSION = array();
public function userLogout()
{
session_destroy();
unset($_SESSION['SESSION']);
return true;
}
Script to access users table information (username, email etc):
$userID = $_SESSION['SESSION'];
$stmt = $userClass->runQuery("SELECT * FROM users WHERE uniqID=:userID");
$stmt->execute(array(":userID"=>$userID));
$user = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($_SESSION['SESSION']); //Prints out session even if not logged in
print $user['Username']; //Prints out nothing
i'm not sure if i missed a step or if hashing a session is even necessary, maybe i am doing something incorrectly. Essentially i am trying to secure the userID via a hash instead of having it displaying the users actual ID. Using the hash i would like to match and gain access to the users column.
ADDITIONAL NOTE: if i change the session to get the actual userID eg: 1
$userID = 1; //i did set the $_SESSION var to the userID to check if logout works
$stmt = $userClass->runQuery("SELECT * FROM users WHERE userID=:userID");
$stmt->execute(array(":userID"=>$userID));
$user = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($_SESSION['SESSION']); //prints out 1 //still does not destroy session after userLogout() is initiated
print $user['Username']; //Prints username which is correct
been boggled by this for hours, maybe a different set of eyes and experience might help x_x.
(this is a comment, but its a bit long)
As ADyson says, this is very confused coding. Neither your code nor your narrative explain what you are trying to achieve here. What is the threat model? What is your definition of "secure"? $_SESSION['SESSION'] is not the session id.
If you store a static map between the (effectively random, but not random enough) identifier stored in the session and the username, then all you've done is limit the performance and scalability of the system - I cannot see how it adds any value for security.
There is a usage model where you might want a warrant-proof user database, where you would hash usernames - but this is not it.
There are models for protecting the session data from other subscribers on a shared host (with poor partitioning). This is not that either.
There are models for ensuring that session data is protected in backups....and, nope, this isn't very good for that either.
sha1(md5(sha1($UID)));
This is silly.
Take some time to understand how the default session handler actually works before you start trying to improve it by throwing code at it (hint: there are things in there which are not suitable for every application, but if you want to improve them, use a custom handler - not wrapping the default handler in additional code). But before you write any code, you need to get your head around exactly what you are trying to achieve.
You need to follow such way:
session_start();
// Unset all of the session variables.
$_SESSION = array();
// Finally, destroy the session.
session_destroy();
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 am new to class development in PHP, however I am still quite confused by the concept of sessions.
Question 1: Can sessions be changed by client manipulations? If not can I set static sessions and use them without validation?
Question 2: How should I be managing my user accounts?
I do use SALT however, a code is generated during registration and inserted into DB where it's used for login reference. Any corrections with explanation would be much appreciated, as well anything about sessions being modified by client.
class user {
private $username = '';
private $password = '';
private $salt = '';
public $prefix = 'rhs_';
function __construct () {
$this->username = '';
$this->password = '';
$this->salt = '';
session_start();
}
public function login ($username, $password) {
$mysql_conn = Database::obtain();
$username = $mysql_conn->escape($username);
$sql = 'SELECT `password`, `salt`, `first_name`, `last_name`, `permission` FROM `accounts` WHERE `username`="'.$username.'"';
$row = $mysql_conn->query_first($sql);
if(!empty($row['password'])) {
$encrypted = md5(md5($mysql_conn->escape($password)).$row['salt']);
if ($encrypted == $row['password']) {
$_SESSION[$this->prefix.'username'] = $username;
$_SESSION[$this->prefix.'password'] = $password;
$_SESSION[$this->prefix.'name'] = $row['first_name'].' '.$row['last_name'];
$_SESSION[$this->prefix.'permission'] = $row['permission'];
header('location: ?page=cpanel');
} else {
return false;
}
} else {
return false;
}
}
A session is a file on your server where variables can be written and saved. Each session file corresponds to one active visitor to your site. PHP automatically deletes files that haven't been read from or written to for ~24 minutes.
Sessions are linked to users by a cookie. When a user browses to your page where you use sessions, PHP checks to see if a specially named cookie was sent with the request, containing their session identifier.
If the cookie exists, that identifier tells PHP which session file to open and read to populate $_SESSION.
If no cookie exists, a new identifier is generated and sent as a cookie to the user, and a new empty session file is created.
Since the sessions are files on your server, your users cannot modify them.
I am new to class development in PHP
Even if you are experienced programmer making an unsafe authentication system is easy as pie. You should be using OpenID(or systems like that like for example facebook connect) instead. They have security-experts as employees. I created a little library you can use for this. You can see a demo at http://westerveld.name/php-openid//
Can sessions be changed by client
manipulations? If not can I set static
sessions and use them without
validation?
It can not be changed by clients, but a users session could be stolen by hackers. You need to prevent session-fixation => session_regenerate_id
How should I be managing my user
accounts?
You probably should not do this, because the change you make a mistake is BIG. But below are some quick tips:
You should use phpass to store your password in the database. Because I did notice you are not using key stretching. You should perform a benchmark, and if you can generate hashes quickly, then you are definitely not securing your passwords safely. Bcrypt is a very good for hashing(used by phpass. You should use that library), because it is Moore's law proof.
Protect your users against CSRF.
You should read the OWASP top 10.
I also have created a little authentication library just for the fun of it. And I think it is pretty safe although for example logout.php is still vulnerable to CSRF although this is not really a big problem(and the fix is very easy).
EDIT: After a complaint about assigning myself the answer, I want to update that the answers provided were not satisfactory. No one came out and explicitly said this is your problem, do this and you will have a resolution. Mere suggestions are not sufficient to merit a bounty award. Lastly, the problem was with server settings and after doing some research on server sessions and looking at Stackoverflow/Serverfault I was able to determine how to best resolve this problem. Therefore, I did not feel it was unjust to mark my own answer as the correct one.
I have a php based authentication system which relies on LDAP to verify identity and uses sessions to maintain users authenticated status.
Lately I noticed that it appears to be pushing me back to the login page like my session expired. The problem is that it does not appear to be for any specific reason that I have noticed and I am not sure how to debug/test something like this.
Here is my authentication function which starts the session:
function authenticateUser($user, $password){
//assuming ldap connection and verification of user login/pass
//this is what will happen with authenticate user which is called
//when user submits login/pass on authentication form.
$_SESSION['id'] = $uID;
$time = time();
$_SESSION['time'] = $time;
$_SESSION['lastActivity'] = $time;
$_SESSION['expiration'] = $time+$cookieExpiration;
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['secret'] = md5(rand());
$_SESSION['userHash'] = getSessionHash();
$_SESSION['firstLogin'] = isFirstLogin($user);
//assign cookie to user and log authentication
giveCookie("userHash", $_SESSION['userHash'],0);
logAuthenticationAttempt($user, $_SERVER['REMOTE_ADDR'], 1);
return true;
}//end authenticateUser
Give cookie function:
function giveCookie($name, $value, $expiration=0){
global $path, $site;
setcookie("userHash", $_SESSION['userHash'], $expiration, $path, $site, true, true);
}//end giveCookie
Here is my function which is called on each page to verify the user is authenticated before allowing them to proceed with action requiring authenticated status:
function isValidUser(){
global $links; global $userName; global $userID; global $cookieExpiration;
if(isset($_COOKIE['userHash']) and isset($_SESSION['userHash'])){
if($_COOKIE['userHash'] == $_SESSION['userHash']){
$userName = $_SESSION['nameN'];
$userID = $_SESSION['id'];
//update userHash cookie for additinoal expiration time this way session
$time = time();
$expiration = $time+$cookieExpiration;
$_SESSION['lastActivity'] = $time;
giveCookie("userHash", $_SESSION['userHash'],0);
$_SESSION['expiration'] = $expiration;
return true;
}
}
return false;
}//end isvalidUser()
Any advice or feedback on how to test this would be appreciated. I am looking to figure out why occasionally after performing some action I get pushed back to the login page.
On a page which request authentication what I do at the top is the following:
if(!isValidUser()){changePage($links['login']."?refer=".$links['requestHelp']);}
//note: changePage is just a function for header("location: somepage.php");
You seem to be confusing authentication, authorization and session management. If you want to test all 3 then you'll need some sort of automated test tool capable of scriptable, stateful HTTP session replay (e.g. http::recorder / www::mechaninze with Perl).
OTOH if you want to investigate the session management using your deployed application, then I'd recommend instrumenting the login page to capture information about the current session and how the user got routed there. You should also consider logging the session cookie on the webserver.
Not sure what testing software you use at the moment, but I'd strongly recommend Selenium. It allows you to run scripted tests through the browser, effectively simulating what an end user would do and see.
Write a functional test. You can use SimpleTest's WebTestCase for stuff like this. See the documentation at: http://www.simpletest.org/en/web_tester_documentation.html
Of course, you could also try to break the code down into smaller bits that can easier be tested individually. Right now, your authentication system is tightly coupled to the server state (eg. the session management). You could decouple the two and thus be able to test the authentication system in a unit test, rather than a functional test.
The server is probably overwriting their session cookie, which will change the session hash and then it won't equal the cookie they have clientside. You appear to be overthinking this. You don't need to set a cookie on their system to verify them. The $_SESSION var will handle it all for you.
If they pass the challenge, then they have a $_SESSION var that is set that gives them auth levels.
Don't pass $_SESSION off to other vars like you are. Don't go $username = $_SESSION['username'] because now you have a var that is nonsecure ($username) that could have secured data in it. But it might not.
Whenever you are displaying session info, make sure it came from the horse's mouth.
Take a deep look at session.configuration
i would (if your not willing to think over your concept) change the isValidUser to log what ever you can get before return false
function isValidUser(){
global $cookieExpiration; // since $links isn't used and $userName and $userId come from $_SESSION no need for global there
if( (isset($_COOKIE['userHash']) and isset($_SESSION['userHash']))
and ($_COOKIE['userHash'] == $_SESSION['userHash']) ){
$userName = $_SESSION['nameN']; // why these lines?
$userID = $_SESSION['id']; // see above
//update userHash cookie for additinoal expiration time this way session
$time = time();
$expiration = $time+$cookieExpiration;
$_SESSION['lastActivity'] = $time;
giveCookie("userHash", $_SESSION['userHash'],0);
$_SESSION['expiration'] = $expiration;
return true;
}
// $logger ist just an example an could be a p.e Zend_Logger Object
$logger->debug($_SESSION); // serialize if needed
$logger->debug($_COOKIE); // serialize if needed
$logger->err(error_get_last());
return false;
}//end isvalidUser()
And are you sure you called session_start() before isValidUser()
It appears that this was a simple misconfiguration of the server itself. Being that this server environment is a shared environment for hundreds of sites, I do not have ability to modify server wide settings in php.ini and the like.
What I was able to do though is...
session_set_cookie_params(time()+$expiration, $path, $domain, true, true);
This has resolved the issue, it appears before the session expiration was not set properly and this will set it accordingly for this specific application.
I have a website where the login info is optionally saved in a cookie (remember me checkbox at login) and when the user logs out, the value of the authentication cookie won't change to expire the cookie allowing logout.
The system does work correctly in both the dev and staging servers but for some reason will not work on our production server. We are running PHP 5 and Apache on all the servers.
Thanks.
Function to set cookie (minor edits for security):
function setCookieInfo($data,$expiry=0)
{
if($data === false)
{
//remove cookie!
$cookie = false;
$expiry = 100; //should be in the past enough!
}
else
{
$serial = base64_encode(serialize($data));
$hash = md5($XXX);
$cookie = $hash."---".$serial;
}
if($_SERVER['SERVER_NAME']=='localhost')
{
$domain = null;
}
else
{
$domain = '.'.$_SERVER['SERVER_NAME'];
}
return setcookie('Auth', $cookie, $expiry, $this->controller->base, $domain);
}
Posting some actual code might help, but I'll hazard a guess that it has something to do with the cookie domain being used.
Grab a traffic capture (e.g. www.fiddler2.com) of the SetCookie call that is intended to delete the cookie, and ensure that the Domain is valid and the expiration time/value is as expected.
Assuming you are using the PHP setcookie() function, make sure that the domain and path for the cookie are set correctly. Check PHP's documentation for the function for more information.
I might be able to tell you for sure if I had a little more info. Can you provide any more information without compromising too much about the project? How about the URLs of the dev, staging, and production servers, or at least examples of what they might be like?
Edit
Based upon the info you provided in your comment, I would recommend that you try using HTTP_HOST instead of SERVER_NAME. SERVER_NAME might be giving you a weird value depending upon your virtual server setup. Your path might not be quite right either - try a '/' and it should be available regardless of the subdirectory the user is in.
Also,
$this->controller->base
makes me think that you might be using CodeIgniter or Kohana. If so, you might consider using their cookie helpers.