Sessions Don't Last, Randomly Logged Out

Discussions of secure PHP coding. Security in software is important, so don't be afraid to ask. And when answering: be anal. Nitpick. No security vulnerability is too small.

Moderator: General Moderators

Post Reply
User avatar
Skara
Forum Regular
Posts: 703
Joined: Sat Mar 12, 2005 7:13 pm
Location: US

Sessions Don't Last, Randomly Logged Out

Post by Skara »

Hi,

I'm having issues with my login code. I randomly get logged out. If I refresh the page a lot, or if I check my login more than once or twice in quick-enough succession in an AJAX call, it happens much more frequently. I can't seem to nail down what's happening. I've checked all my files for extra spaces at the top, nothing is being sent before session_start() runs. Even if an error is called, any error output is suppressed if the user isn't logged in. (Errors are logged to an array and output all at once, if at all.)

Generally I stay logged in for anywhere between 30 minutes and two or three hours before I'm randomly no longer logged in.

Also, I have a logging system (not shown) that logs my IP and user agent string for most actions I do, and I can confirm my IP and UA aren't changing.

Any help you have would be appreciated. I'm at my wit's end.

This is what I have, abbreviated a bit:

Code: Select all

/** in definitions.php */
define('LOGIN_EXPIRE',28800); //28800 = 8hrs

/** auth_functions.php */
function sec_session_start() {
    ini_set('session.use_only_cookies', 1);
    session_set_cookie_params(LOGIN_EXPIRE, '/', '.example.com', false, true); //example is set to my domain, of course
    session_name('naga_sec_session_id');
    session_start();
    session_regenerate_id(true);
}

function login($username, $password) {
    global $db, $_user, $_now;

    $username = preg_replace('/[^a-zA-Z0-9_\-]+/','',$username);
    $res = @$db->query("SELECT users.id AS user_id, users.* FROM users WHERE users.username='$username' LIMIT 1;");
    if (!$res) {
        //give no info, return
        return false;
    }
    if ($res->num_rows) {
        $row = $res->fetch_assoc();
        $user_id = $row['id'];

        if(checkbrute($user_id) == true) {
            //send email here
            return false;
        }
        else {
            $user_pass = $row['password'];
            $user_salt = $row['salt'];

            $check_pass = hash('sha512', $password.$user_salt);

            if ($user_pass == $check_pass) {
                $ip_address = $_SERVER['REMOTE_ADDR'];
                $user_browser = $_SERVER['HTTP_USER_AGENT'];

                $user_id = preg_replace("/[^0-9]+/", "", $user_id);
                $_SESSION['naga_sess_user_id'] = $user_id;
                $_SESSION['naga_sess_username'] = $username;
                $_SESSION['naga_sess_login_string'] = hash('sha512', $user_pass.$ip_address.$user_browser);

                $_user = array(
                    'id' => $user_id,
                    'user' => $username,
                    //abbreviated
                );

                return true;
            }
            else {
                // Password not correct
                @$db->query("INSERT INTO `user_loginattempts` VALUES ('$user_id','$_now');");

                return false;
            }
        }
    }
    else {
        //no such user exists
        return false;
    }
}

function checkbrute($user_id) {
    global $db, $_now;

    $user_id = intval($user_id);
    $valid_attempts = $_now - (2 * 60 * 60);

    $res = $db->query("SELECT `time` FROM `user_loginattempts` WHERE `user_id`='$user_id' AND `time` > '$valid_attempts';");

    if ($res && $res->num_rows > 5) {
        return true;
    }
    else {
        return false;
    }
}


function login_check() {
    global $db, $_user;

    if(isset($_SESSION['naga_sess_user_id'], $_SESSION['naga_sess_username'], $_SESSION['naga_sess_login_string'])) {
        $user_id = intval($_SESSION['naga_sess_user_id']);
        $login_string = $_SESSION['naga_sess_login_string'];
        $username = $_SESSION['naga_sess_username'];
        $ip_address = $_SERVER['REMOTE_ADDR'];
        $user_browser = $_SERVER['HTTP_USER_AGENT'];

        $res = @$db->query("SELECT users.id AS user_id, users.* FROM users WHERE users.username='$username' LIMIT 1;");

        if (!$res) {
            // Not logged in
            return false;
        }
        elseif ($res->num_rows == 1) {
            $row = $res->fetch_assoc();
            $login_check = hash('sha512', $row['password'].$ip_address.$user_browser);
            if($login_check == $login_string) {
                // Logged In

                $_user = array(
                    'id' => $user_id,
                    'user' => $username,
                    //abbreviated
                );
                return true;
            }
            else {
                return false;
            }
        }
        else {
            return false;
        }
    }
    else {
        return false;
    }
}
function print_login_form() {
    //outputs login form
}

Code: Select all

/** auth.php */
$_user = array(
    'id' => 0,
    'user' => 'anonymous'
    //abbreviated
);

sec_session_start();

if(login_check() != true) {
    if(isset($_POST['username'], $_POST['password'])) { 
        $username = $_POST['username'];
        $password = $_POST['password']; // The hashed password
        if(login($username, $password) == true) {
            // Login success
        }
        else {
            // Login failed
            print_login_form();
       }
    }
    else {
        print_login_form();
    }
}

Code: Select all

/** init.php (abbreviated); same/similar to header of any files called by AJAX queries */

// DEFINE BASIC INFORMATION
require_once('definitions.php'); // define() s
require_once('version.php'); // version information

// LOAD LIBRARIES
require_once('errors.php'); // sets up error handling
require_once('functions.php'); // various generic functions
require_once('lang.php'); // language file

// LOAD DATABASE
require_once('db.php'); // class db extends mysqli

// AUTHENTICATE USER
require_once('auth_functions.php');
require_once('auth.php');

//... after this, begin running application ...
Post Reply