0

Obviously I am not the only one experiencing this problem. However, I am unable to fix it based on other user postings I found related to this error-message.

I get the following error:

( ! ) Warning: session_regenerate_id(): Cannot regenerate session id - headers already sent in C:\wamp\www\Creating-Secure-PHP-Websites\05_authentication\sample_app\private\functions\session_hijacking_functions.php on line 113
Call Stack
#   Time    Memory  Function    Location
1   0.0000  244440  {main}( )   ..\login.php:0
2   0.0830  322816  after_successful_login( )   ..\login.php:33
3   0.0830  322864  session_regenerate_id ( )   ..\session_hijacking_functions.php:113

( ! ) Warning: Cannot modify header information - headers already sent by (output started at C:\wamp\www\Creating-Secure-PHP-Websites\05_authentication\sample_app\public\login.php:3) in C:\wamp\www\Creating-Secure-PHP-Websites\05_authentication\sample_app\private\functions\general_functions.php on line 7
Call Stack
#   Time    Memory  Function    Location
1   0.0000  244440  {main}( )   ..\login.php:0
2   0.0990  323120  redirect_to( )  ..\login.php:34
3   0.0990  323256  header ( )  ..\general_functions.php:7

Login.php:

<?php require_once("../private/initialize.php"); ?>

<?php

// Rather than require setting up a real database, 
// we can fake one instead.
initialize_fake_database();

// initialize variables to default values
$username = "";
$password = "";
$message = "";

if(request_is_post() && request_is_same_domain()) {

  if(!csrf_token_is_valid() || !csrf_token_is_recent()) {
    $message = "Sorry, request was not valid.";
  } else {
    // CSRF tests passed--form was created by us recently.

        // retrieve the values submitted via the form
    $username = $_POST['username'];
    $password = $_POST['password'];

        if(has_presence($username) && has_presence($password)) {

            // Search our fake database to retrieve the user data
            $sqlsafe_username = sql_prep($username);
            $user = find_one_in_fake_db('users', 'username', $sqlsafe_username);

        if($user && password_verify($password, $user['hashed_password'])) {
            // successful login
          after_successful_login();
          redirect_to('private.php');
        } else {
          // failed login
          $message = "Username/password combination not found.";
        }

        } else {
            // username or password left blank, just re-display the form.
        }
  }
}

?>

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Log in</title>
  </head>
  <body>

    <?php
      if($message != "") {
        echo '<p>' . h($message) . '</p>';
      }
    ?>

    <p>Please log in.</p>

    <form action="login.php" method="POST" accept-charset="utf-8">
      <?php echo csrf_token_tag(); ?>
      Username: <input type="text" name="username" value="<?php echo h($username); ?>" /><br />
            <br />
      Password: <input type="password" name="password" value="" /><br />
            <br />
      <input type="submit" name="submit" value="Log in" />
    </form>

<?php
// Uncomment if you want to examine the contents of the fake database
// echo "<br /><br />";
// echo "--- fake database contents ---";
// var_dump($_SESSION['fake_database']);
// echo "------------------------------";
?>
  </body>
</html>

general_functions.php:

<?php
// Put all of your general functions in this file

// header redirection often requires output buffering 
// to be turned on in php.ini.
function redirect_to($new_location) {
  header("Location: " . $new_location);
  exit;
}

?>

session_hijacking.php:

<?php
// Useful php.ini file settings:
// session.cookie_lifetime = 0
// session.cookie_secure = 1
// session.cookie_httponly = 1
// session.use_only_cookies = 1
// session.entropy_file = "/dev/urandom"

// Must have already called:
// session_start();

// Function to forcibly end the session
function end_session() {
    // Use both for compatibility with all browsers
    // and all versions of PHP.
    session_unset();
  session_destroy();
}

// Does the request IP match the stored value?
function request_ip_matches_session() {
    // return false if either value is not set
    if(!isset($_SESSION['ip']) || !isset($_SERVER['REMOTE_ADDR'])) {
        return false;
    }
    if($_SESSION['ip'] === $_SERVER['REMOTE_ADDR']) {
        return true;
    } else {
        return false;
    }
}

// Does the request user agent match the stored value?
function request_user_agent_matches_session() {
    // return false if either value is not set
    if(!isset($_SESSION['user_agent']) || !isset($_SERVER['HTTP_USER_AGENT'])) {
        return false;
    }
    if($_SESSION['user_agent'] === $_SERVER['HTTP_USER_AGENT']) {
        return true;
    } else {
        return false;
    }
}

// Has too much time passed since the last login?
function last_login_is_recent() {
    $max_elapsed = 60 * 60 * 24; // 1 day
    // return false if value is not set
    if(!isset($_SESSION['last_login'])) {
        return false;
    }
    if(($_SESSION['last_login'] + $max_elapsed) >= time()) {
        return true;
    } else {
        return false;
    }
}

// Should the session be considered valid?
function is_session_valid() {
    $check_ip = true;
    $check_user_agent = true;
    $check_last_login = true;

    if($check_ip && !request_ip_matches_session()) {
        return false;
    }
    if($check_user_agent && !request_user_agent_matches_session()) {
        return false;
    }
    if($check_last_login && !last_login_is_recent()) {
        return false;
    }
    return true;
}

// If session is not valid, end and redirect to login page.
function confirm_session_is_valid() {
    if(!is_session_valid()) {
        end_session();
        // Note that header redirection requires output buffering 
        // to be turned on or requires nothing has been output 
        // (not even whitespace).
        header("Location: login.php");
        exit;
    }
}


// Is user logged in already?
function is_logged_in() {
    return (isset($_SESSION['logged_in']) && $_SESSION['logged_in']);
}

// If user is not logged in, end and redirect to login page.
function confirm_user_logged_in() {
    if(!is_logged_in()) {
        end_session();
        // Note that header redirection requires output buffering 
        // to be turned on or requires nothing has been output 
        // (not even whitespace).
        header("Location: login.php");
        exit;
    }
}


// Actions to preform after every successful login
function after_successful_login() {
    // Regenerate session ID to invalidate the old one.
    // Super important to prevent session hijacking/fixation.
    session_regenerate_id();

    $_SESSION['logged_in'] = true;

    // Save these values in the session, even when checks aren't enabled 
  $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
  $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
    $_SESSION['last_login'] = time();

}

// Actions to preform after every successful logout
function after_successful_logout() {
    $_SESSION['logged_in'] = false;
    end_session();
}

// Actions to preform before giving access to any 
// access-restricted page.
function before_every_protected_page() {
    confirm_user_logged_in();
    confirm_session_is_valid();
}


// Uncomment to demonstrate usage

// if(isset($_GET['action'])) {
//  if($_GET['action'] == "login") {
//      after_successful_login();
//  }
//  if($_GET['action'] == "logout") {
//      after_successful_logout();
//  }
// } 
// 
// echo "Session ID: " . session_id() . "<br />";
// echo "Logged in: " . (is_logged_in() ? 'true' : 'false') . "<br />";
// echo "Session valid: " . (is_session_valid() ? 'true' : 'false') . "<br />";
// echo "<br />";
// echo "--- SESSION ---<br />";
// var_dump($_SESSION);
// echo "--------------------<br />";
// echo "<br />";
// 
// echo "<a href=\"?action=new_page\">Simulate a new page request</a><br />";
// echo "<a href=\"?action=login\">Simulate a log in</a><br />";
// echo "<a href=\"?action=logout\">Simulate a log out</a>";

?>
user838531
  • 478
  • 5
  • 14

1 Answers1

1

Quick and dirty answer: Make sure you do NOT output anything before you call session_regenerate_id().

When you output data you will start serving a response, which will send the HTTP response headers. You cannot change the HTTP headers (regenerate the session cookie ID) after they are already sent.

Looking at your script you should remove the first php closing tag ?> and the opening tag <?php on line 3:

Login.php:

<?php require_once("../private/initialize.php");


// Rather than require setting up a real database, 
// we can fake one instead.
initialize_fake_database();

Why is this so? If your .php script outputs HTML directly, PHP views everything outside <?php ?> tags as output. Even starting your file with a new line will already produce output and will cause errors when you want to set HTTP response headers.

To migitate this you could consider using ONLY PHP in your .php files and output your html via templates. This would be a first step to using a framework, which I would totally recommend anyway.

Hikariii
  • 331
  • 2
  • 7