Session ID regeneration not working on windows - php

I have the following code which works fine under several linux flavors I have tried (Ubuntu, Debian 8, CentOS 7), however when I use it on windows, the regeneration fails without an error. $duration is a value in the class represented as static $duration = 60 * SESSION_TIMEOUT; where SESSION_TIMEOUT is a constant defined in a config.inc.php file (user setting).
session_start();
self::csrf(false);
if(self::verify(false) === true) {
$_SESSION['expires'] = time() + self::$duration;
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
If I echo $id after the line $id = session_id(); there is a value, however if I echo session_id() after the last session_start(), it is empty. (See session_regenerate_id if you think this method is 'Dodgy' )
I do not know why this regeneration code is failing :
session_regenerate_id(true);
$id = session_id();
// one of the lines below this are causing session_id() to be blank
session_write_close();
session_id($id);
session_start();
Please assist me in identifying what is causing the new session to be blank when it should contain the new session id.
session.save_path = 2;755;d:/server/www/127.0.0.1/sessions
Update: I was able to find this in the log, however the interesting part is the session file is created with data.
PHP Warning: Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (2;755;d:/server/www/127.0.0.1/sessions) in Unknown on line 0
As requested, a sample of $id before calling session_write_close() (and subsequent code), is 9vdom0ghuqkvsdcnkjacurc6rf2n4fn1s1gvfva44okd15jdpm30
Update:
After some more testing, I have traced the problem to be a bit more complex than stated above. The problem I initially had which I believed was a session regeneration issue, is more of a problem where session variables are not being stored. I believe this is still related to session regeneration somewhat on windows, however I still am unable to pinpoint a fix, as this code works beautifully on Linux.
Here is the class (minified):
class session {
static $duration = 60 * SESSION_TIMEOUT;
public static function start() {
session_start();
self::csrf(false);
if(self::verify(false) === true) {
$_SESSION['expires'] = time() + self::$duration;
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
}
public static function csrf($new = false) {
if(!isset($_SESSION['csrf']) || $new === true) {
$_SESSION['csrf'] = sha1(openssl_random_pseudo_bytes(mt_rand(16,32)));
}
return $_SESSION['csrf'];
}
}
The code used to call it, is rather complex (when it comes to determining when to generate a new csrf token), but below is my attempt to extract a chunk from my overall project to demonstrate the usage. The login code behind should suffice I believe to view the logic:
if( session::verify() === true ) {
redirect();
} else {
$error = '';
$token = $_SESSION['csrf'];
if ( !empty($_POST) ) {
if ( isset($_POST['csrf']) ) {
// check CSRF token and if match ti token stored in session
$csrf = trim(filter_input(INPUT_POST, 'csrf', FILTER_SANITIZE_SPECIAL_CHARS, array('flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH)));
if($csrf != $_SESSION['csrf']) { session::destroy(); $error = 'CSRF Attack Detected'; }
if( isset($_POST['username']) && isset($_POST['password']) && empty($error) ) {
// login validation code
if($auth) {
// regenerate csrf token
$token = session::csrf(true);
session::create( $username );
// redirect back to application root
redirect();
}
$error = 'Invalid credentials';
} else {
if(empty($error)) { $error = 'Invalid credentials'; }
}
// user was not authenticated, regenerate csrf token to prevent form spam
$token = session::csrf(true);
} else {
// CSRF token did not match stored token in session
$error = 'CSRF Attack Detected';
}
}
}
The additional methods for my session class as used in the login code are as follows :
Update session data with user information.
public static function create($user) {
$_SESSION['nonce'] = sha1(microtime(true));
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['agent'] = sha1($_SERVER['HTTP_USER_AGENT']);
$_SESSION['expires'] = time() + self::$duration;
$_SESSION['user'] = $user;
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
}
Verify posted information matches server session
public static function verify($destroy = false) {
$valid = true;
try {
if( !isset($_SESSION['nonce']) ) { $valid = false; }
if( !isset($_SESSION['user']) ) { $valid = false; }
if( isset($_SESSION['ip']) ) { if($_SESSION['ip'] != $_SERVER['REMOTE_ADDR']) { $valid = false; } } else { $valid = false; }
if( isset($_SESSION['agent']) ) { if($_SESSION['agent'] != sha1($_SERVER['HTTP_USER_AGENT']) ) { $valid = false; } } else { $valid = false; }
if( isset($_SESSION['expires']) ) { if($_SESSION['expires'] <= time()) { $valid = false; } } else { $valid = false; }
} catch (Exception $e) {
$valid = false;
}
if($valid === false) {
if(isset($_SESSION['nonce'])) { unset($_SESSION['nonce']); }
if(isset($_SESSION['ip'])) { unset($_SESSION['ip']); }
if(isset($_SESSION['agent'])) { unset($_SESSION['agent']); }
if(isset($_SESSION['expires'])) { unset($_SESSION['expires']); }
if(isset($_SESSION['user'])) { unset($_SESSION['user']); }
if($destroy === true) {
session_unset();
session_destroy();
}
}
return $valid;
}
Update:
Narrowed this down by running a simple test :
// ensure sessions are writeable
if(!is_writable(session_save_path())) {
// try alternate session path
$_session_save_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'sessions';
if(!is_writable($_session_save_path)) {
echo "Can't write sessions"; exit;
} else {
session_save_path(
$_session_save_path
);
}
}
Which echo's the result 'Can't write sessions' meaning it failed all methods to try and write data.
What is interesting here, is that no matter what I do (even setting the root of the drive to 'Everyone' with 'Full' permissions, all subfolders and files seem to have a permanent semi-shaded 'Read Only' checkbox. Beyond this, file_put_contents("hello.txt", "hello"); exit(); works in the root folder of my local site, but not from any subfolder below 2 directories deep -- even after assigning (and checking) permissions. (e.g. d:\server\websites\127.0.0.1\htdocs\ works, but not d:\server\websites\127.0.0.1\htdocs\path1\path2\
Update:
Did some more troubleshooting, and the rabbit hole gets deeper. Ran a script to find what user PHP is running under to better assist in checking permissions:
echo 'Current script owner: ' . get_current_user();
which echo's 'Current script owner SYSTEM'
I followed up with this code :
$new = false;
session_start();
if(!isset($_SESSION['csrf']) || $new === true) {
$_SESSION['csrf'] = sha1(openssl_random_pseudo_bytes(mt_rand(16,32)));
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
Which still failed. I did try something which finally worked, but I really don't feel comfortable with it as it involves not setting the security bits as documented in the php.ini file.
I changed :
session.save_path = "2;755;d:/server/www/127.0.0.1/sessions"
To
session.save_path = "d:/server/www/127.0.0.1/sessions"
And this works, however all the session files are now world accessible.
(Server is the same version of apache and php on all platforms configured with identical php.ini files, and server configuration files as close as possible)

Related

Can't access session data session_write_close

I was having issues with the session file being locked, so I added session_write_close() once I was done with the session. The script worked properly before that, however, once I leave the sign-in page now, the session is blank.
Session is started at the top of index.php which includes the sign in page:
$result = 'token_valid';
$_SESSION['user'] = $email;
print_r($_SESSION);
session_write_close();
print_r($_SESSION);
The session data is returned properly both times on the sign-in page.
Array ( [user] => abc#gmail.com ) Array ( [user] => abc#gmail.com )
A link returns to the home page, which calls a function to check if logged in...
function user_is_signed_in() {
print_r($_SESSION);
session_write_close();
if($user == '') {
return False;
}
else {
return True;
}
}
The session no longer has any data.
Full index.php
<?php
session_start();
include_once('fnc/database.php');
include_once('fnc/user.php');
if(!user_is_signed_in()) {
include('sign-in.php');
}
else {
$url = parse_url($_SERVER['REQUEST_URI']);
if(!$url['query'])
{
include('home.php');
}
else {
if(isset($_GET['media']))
{
include($_GET['media'].'.php');
}
if(isset($_GET['user']))
{
include($_GET['user'].'.php');
}
}
}
.
.
Workaround (probably filthy)
Issue seems to be caused by the reading/writing of the actual session file. Used the session_id generated by PHP and just created a secondary session file. Do not save in same folder (or if you do, change the filename) - session_start seems to delete and regenerate the session file PHP manages and you'll lose any data written there.
session_start();
$sess = array();
$sess = $_SESSION;
$sess["id"] = session_id();
//print_r($sess);
session_write_close();
Create session_data in session folder
$session_details = "user|".$email;
$session_file = "/Programs/XAMPP/tmp/session_data/sess_".$sess["id"];
//echo $session_details;
$fh = fopen($session_file, 'w+');
fwrite($fh, $session_details);
fclose($fh);
Read session data from this file instead of the session
$session_path = "/Programs/XAMPP/tmp/session_data/sess_".$sess["id"];
$fh = fopen($session_path, 'r');
$session_file = fread($fh, filesize($session_path));
$exploded_session = explode("\n", $session_file);
$session_data = array();
foreach($exploded_session as $line)
{
$tmp = explode("|", $line);
$session_data[$tmp[0]] = $tmp[1];
}
return $session_data["user"];
fclose($fh);

PHP - Cannot change session variable in an "if" statement

Trying to make a page redirect if not logged in. I'll try to show what I mean.
In the index.php:
<?php
// initialization of login system and generation code
$oSimpleLoginSystem = new SimpleLoginSystem();
echo $oSimpleLoginSystem->getLoginBox();
session_start();
$_SESSION["loggedin"] = False;
// class SimpleLoginSystem
class SimpleLoginSystem {
// variables
var $aExistedMembers; // Existed members array
// constructor
function SimpleLoginSystem() {
$this->aExistedMembers = array(
'test' => MD5('test'), //Sample: MD5('qwerty')
);
}
function getLoginBox() {
ob_start();
require_once('login_form.html');
$sLoginForm = ob_get_clean();
$sLogoutForm = 'logout';
if ((int)$_REQUEST['logout'] == 1) {
if (isset($_COOKIE['member_name']) && isset($_COOKIE['member_pass']))
$this->simple_logout();
}
if ($_REQUEST['username'] && $_REQUEST['password']) {
if ($this->check_login($_REQUEST['username'], MD5($_REQUEST['password'])))
{
$_SESSION["loggedin"] = True;
$this->simple_login($_REQUEST['username'], $_REQUEST['password']);
header( 'Location: /site.html' );
}
else {
return 'Username or Password is incorrect' . $sLoginForm;
}
} else {
if ($_COOKIE['member_name'] && $_COOKIE['member_pass']) {
if ($this->check_login($_COOKIE['member_name'], $_COOKIE['member_pass'])) {
header( 'Location: /site.html' );
}
}
return $sLoginForm;
}
}
In the site.php:
<?php
session_start();
if ($_SESSION["loggedin"] == 0)
{
header( 'Location: http://test-weeabear.c9users.io/' );
}
else
{
return "It worked!"
}
?>
Why is it when I open site.html, it redirects me back to index.html, even after logging in?
NOTE: Some things I changed, like links and names of files. For privacy.
You need session_start() at the start of your site.php
change the file extension from .html to .php
You are calling getLoginBox and then changing the loggedin back to false. You need to include an exit after the header redirect.
Like this:
header( 'Location: /site.php' );//changed to file extension as .php
exit;

Can I open/close a session in PHP multiple times (and if so what is going wrong)?

So this is how my login process works:
authenticate.php
sessionStart();
if (isset($_SESSION) && !empty($_SESSION['LOCATION'])) {
$location = $_SESSION['LOCATION'];
unset($_SESSION['LOCATION']);
} else {
$location = '//' . $_SERVER['SERVER_NAME'];
}
session_write_close();
sessionStart();
$userIsOnline = isset($_SESSION['ID']);
session_write_close();
sessionStart();
if (!$userIsOnline) {
// Get the user from the database
// Validate the user's password
$_SESSION['ID'] = $user->id;
$_SESSION['UN'] = $user->un;
// ... more information
}
session_write_close();
header($location);
exit();
The contents of the sessionStart function:
if (session_id() == '') {
session_name('MyWebsite');
session_set_cookie_params(86400, '/', $_SERVER['SERVER_NAME'], true, true);
session_start();
$_SESSION['LAST_ACTIVITY'] = time();
$_SESSION['CREATED'] = time();
}
Then on the top of every page on my website:
sessionStart();
print_r($_SESSION);
$_SESSION['LOCATION'] = $_SERVER['REQUEST_URI'];
session_write_close();
Prints an empty array. So for some reason, it is wiping my session array during the redirect? Anyone have any ideas?
Also, the values of CREATED and LAST_ACTIVITY are from this question.
If it is not the issue that HTTPS is not used, but the session cookie is set to Secure then my other thought is to change
if (session_id() == '') {
session_name('MyWebsite');
session_set_cookie_params(86400, '/', $_SERVER['SERVER_NAME'], true, true);
to
if (session_name('MyWebsite') != 'MyWebsite') {
session_set_cookie_params(86400, '/', $_SERVER['SERVER_NAME'], true, true);
I wonder if it is giving you a Session ID under a different name, which is why print_r($_SESSION); is coming up empty. If not, I'm out of ideas!

Detect if cookies are enabled in PHP

I am trying to detect if a user on my page has cookies enabled or not. The following code performs the check, but, I have no idea on how to redirect the user to the page they came from.
The script starts a session and checks if it has already checked for cookies. If not, it redirects the user to a test page, and since I had called session_start() in the first page, I should see the PHPSESSID cookie if the user agent has cookies enabled.
The problem is, ths script might be called from any page of my site, and I will have to redirect them back to their selected page, say index.php?page=news&postid=4.
session_start();
// Check if client accepts cookies //
if (!isset($_SESSION['cookies_ok'])) {
if (isset($_GET['cookie_test'])) {
if (!isset($_COOKIE['PHPSESSID'])) {
die('Cookies are disabled');
} else {
$_SESSION['cookies_ok'] = true;
header(-------- - ? ? ? ? ? -------- -);
exit();
}
}
if (!isset($_COOKIE['PHPSESSID'])) {
header('Location: index.php?cookie_test=1');
exit();
}
}
I think its better to make one file set cookie and redirect to another file. Then the next file can check the value and determine if cookie is enabled. See the example.
Create two files, cookiechecker.php and stat.php
// cookiechecker.php
// save the referrer in session. if cookie works we can get back to it later.
session_start();
$_SESSION['page'] = $_SERVER['HTTP_REFERER'];
// setting cookie to test
setcookie('foo', 'bar', time()+3600);
header("location: stat.php");
and
stat.php
<?php if(isset($_COOKIE['foo']) && $_COOKIE['foo']=='bar'):
// cookie is working
session_start();
// get back to our old page
header("location: {$_SESSION['page']}");
else: // show the message ?>
cookie is not working
<? endif; ?>
Load cookiechecker.php in browser it'll tell cookie is working. Call it with command line like curl. It'll say, cookie is not working
Update
Here is a single file solution.
session_start();
if (isset($_GET['check']) && $_GET['check'] == true) {
if (isset($_COOKIE['foo']) && $_COOKIE['foo'] == 'bar') {
// cookie is working
// get back to our old page
header("location: {$_SESSION['page']}");
} else {
// show the message "cookie is not working"
}
} else {
// save the referrer in session. if cookie works we can get back to it later.
$_SESSION['page'] = $_SERVER['HTTP_REFERER'];
// set a cookie to test
setcookie('foo', 'bar', time() + 3600);
// redirecting to the same page to check
header("location: {$_SERVER['PHP_SELF']}?check=true");
}
HTTP_REFERER did not work for me, seems like REQUEST_URI is what I need.
Here is the code I finally used:
session_start();
// ------------------------------- //
// Check if client accepts cookies //
// ------------------------------- //
if( !isset( $_SESSION['cookies_ok'] ) ) {
if( isset( $_GET['cookie_test'] ) ) {
if( !isset( $_COOKIE['PHPSESSID'] ) ) {
die('Cookies are disabled');
}
else {
$_SESSION['cookies_ok'] = true;
$go_to = $_SESSION['cookie_test_caller'];
unset( $_SESSION['cookie_test_caller'] );
header("Location: $go_to");
exit();
}
}
if( !isset( $_COOKIE['PHPSESSID'] ) ){
$_SESSION['cookie_test_caller'] = $_SERVER['REQUEST_URI'];
header('Location: index.php?cookie_test=1');
exit();
}
}
// ------------------------------- //
There's no need to save the original URL and redirect to it afterwards. You can perform a transparent redirect via AJAX which doesn't trigger a page reload. It's very simple to implement. You can check my post here: https://stackoverflow.com/a/18832817/2784322
I think this is easiest solution. Doesn't require separate files and allows you to proceed with script if cookies are enabled:
$cookiesEnabled = true;
if (!isset($_COOKIE['mycookie'])) {
$cookiesEnabled = false;
if (!isset($_GET['cookie_test'])) {
setcookie('mycookie', 1, 0, '/');
#ob_end_clean();
$_SESSION['original_url'] = $_SERVER['REQUEST_URI'];
$uri = $_SERVER['REQUEST_URI'];
$uri = explode('?', $uri);
$q = (isset($uri[1]) && $uri[1])?explode('&', $uri[1]):array();
$q[] = 'cookie_test=1';
$uri[1] = implode('&', $q);
$uri = implode('?', $uri);
header('Location: '.$uri);
die;
}
} else if (isset($_GET['cookie_test'])) {
#ob_end_clean();
$uri = $_SESSION['original_url'];
unset($_SESSION['original_url']);
header('Location: '.$uri);
die;
}
// if (!$cookiesEnabled) ... do what you want if cookies are disabled

Redirecting cookieless sessions in PHP without clicking a link

I've been fighting with the cookieless sessions solution. Of course cookieless sessions solution is amazing. I have a trouble in implementing it because I can't read the session information after redirecting to another page.
Here's my test code in testcode.php
<?php
ini_set('session.use_trans_sid', '1');
session_start();
if (isset($_GET['pagecode'])) {
session_id($_GET['pagecode']);
print_r($_SESSION); // **cannot read session information here**
exit();
}
if (isset($_SESSION['cookieconfirmed']) && $_SESSION['cookieconfirmed'] == 1) {
} else {
/** Checks if the user's browser is cookie-enabled **/
if (isset($_GET['redirected'])) { // if the page has gotten redirected
$_SESSION['cookieconfirmed'] = 1; // confirmed the cookie-disability
if (isset($_COOKIE['testcookie'])) {
header ('location: testcode.php');
} else {
header('location: testcode.php?pagecode=' . session_id());
}
} else {
setcookie('testcookie', 'OK'); //sets a test cookie.
header('location: testcode.php?redirected=1'); // redirects the page to check cookie-disability
}
exit(0);
}
?>
As you can see this code doesn't work. but if i redirect to another page by clicking a link it works well. Here's the code in testcode.php:
<?php
ini_set('session.use_trans_sid', '1');
session_start();
if (isset($_GET['pagecode'])) {
session_id($_GET['pagecode']);
print_r($_SESSION); // **able to read session information here**
exit();
}
if (isset($_SESSION['cookieconfirmed']) && $_SESSION['cookieconfirmed'] == 1) {
} else {
/** Checks if the user's browser is cookie-enabled **/
if (isset($_GET['redirected'])) { // if the page has gotten redirected
$_SESSION['cookieconfirmed'] = 1; // confirmed the cookie-disability
if (isset($_COOKIE['testcookie'])) {
header ('location: testcode.php');
} else {
echo 'Click here to continue';
}
} else {
setcookie('testcookie', 'OK'); //sets a test cookie.
header('location: testcode.php?redirected=1'); // redirects the page to check cookie-disability
}
exit(0);
}
?>
How can I get this to work without clicking a link?
ini_set('session.use_trans_sid', '1');
You have to have this on every single one of your PHP pages - you can't do it just within the session handling script. If it's not on when PHP generates a page, it won't insert the session ID into forms and urls on that page. As such, it'd be better if you put this into your php.ini, or at least httpd.conf/.htaccess (as a php_value) to make it a global option for all scripts.
PHP function for this is :
function append_sid($link) {
if(session_id() !== NULL && !isset($_COOKIE['PHPSESSID'])) {
if(strpos($link, "?") === FALSE) {
return $link . "?PHPSESSID=" . session_id();
} else {
return $link . "&PHPSESSID=" . session_id();
}
} else {
return $link;
}
}
Javascript Function for this is:
function append_sid(link) {
<?php if(session_id() !== NULL && !isset($_COOKIE['PHPSESSID'])) { ?>
var session_id = '<?php echo session_id ?>';
if(link.indexOf('?') == -1) {
return link + '?PHPSESSID=' + session_id;
} else {
return link + '&PHPSESSID=' + session_id;
}
<?php } else { ?>
return link;
<?php } ?>
}
A caveat – passing session id by URL requires the session.session.use_trans_sid tio be set to 1.
php_value session.use_trans_sid = 1 in the .htaccess file. You can also try the function:
ini_set('session.use_trans_sid', '1')

Categories