Understanding PHP Sessions


Sessions allow you to persist user data across multiple page requests. Unlike cookies (which store data on the client side), sessions store data on the server while giving the client a session ID.



How Sessions Work

  1. Client makes first request to server
  2. Server creates a unique session ID (e.g., PHPSESSID=abc123)
  3. Server stores session data in a temporary file (typically in /tmp/ on Linux)
  4. Session ID is sent to client via cookie or URL
  5. Subsequent requests include the session ID to access stored data


Basic Session Usage

Starting a Session

<?php
// Must be called before any output
session_start();

// Store data in session
$_SESSION['username'] = 'john_doe';
$_SESSION['last_login'] = time();

// Access session data
echo "Welcome back, " . $_SESSION['username'];
?>

Destroying a Session

<?php
session_start();

// Remove all session variables
session_unset();

// Destroy the session
session_destroy();

// Optional: Delete session cookie
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(), 
        '', 
        time() - 42000,
        $params["path"], 
        $params["domain"],
        $params["secure"], 
        $params["httponly"]
    );
}
?>


Critical Session Security Measures

1. Secure Session Configuration

Always set these in php.ini or via ini_set():

; Only transmit cookies over HTTPS
session.cookie_secure = 1

; Prevent JavaScript access to session cookie
session.cookie_httponly = 1

; Use strict session ID mode
session.use_strict_mode = 1

; Regenerate ID on login
session.regenerate_id = 1

; Set custom session save path (not /tmp)
session.save_path = "/var/www/secure_sessions"

2. Session Fixation Protection

session_start();

// Regenerate ID when privilege level changes
if (!isset($_SESSION['created'])) {
    session_regenerate_id(true);
    $_SESSION['created'] = time();
} 
// Regenerate every 30 minutes
elseif (time() - $_SESSION['created'] > 1800) {
    session_regenerate_id(true);
    $_SESSION['created'] = time();
}

3. Session Hijacking Prevention

// Check IP consistency
if (isset($_SESSION['ip']) && $_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
    session_unset();
    session_destroy();
    die("Security violation detected");
}

// Check User-Agent consistency
if (isset($_SESSION['ua']) && $_SESSION['ua'] !== $_SERVER['HTTP_USER_AGENT']) {
    session_unset();
    session_destroy();
    die("Security violation detected");
}

// Set these on first request
if (!isset($_SESSION['ip'])) {
    $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
    $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];
}

4. Session Timeout Control

// Set timeout to 30 minutes
$timeout = 1800;

// Check timeout
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > $timeout)) {
    session_unset();
    session_destroy();
    header("Location: /login.php?timeout=1");
    exit;
}

$_SESSION['last_activity'] = time();


Advanced Session Management

Database-Backed Sessions

class DBSessionHandler implements SessionHandlerInterface {
    private $pdo;
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    public function open($savePath, $sessionName) {
        return true;
    }
    
    public function close() {
        return true;
    }
    
    public function read($id) {
        $stmt = $this->pdo->prepare("SELECT data FROM sessions WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetchColumn() ?: '';
    }
    
    public function write($id, $data) {
        $stmt = $this->pdo->prepare(
            "REPLACE INTO sessions (id, data, access) VALUES (?, ?, ?)"
        );
        return $stmt->execute([$id, $data, time()]);
    }
    
    public function destroy($id) {
        $stmt = $this->pdo->prepare("DELETE FROM sessions WHERE id = ?");
        return $stmt->execute([$id]);
    }
    
    public function gc($maxlifetime) {
        $stmt = $this->pdo->prepare(
            "DELETE FROM sessions WHERE access < ?"
        );
        return $stmt->execute([time() - $maxlifetime]);
    }
}

// Usage
$pdo = new PDO("mysql:host=localhost;dbname=secure_app", "user", "password");
$handler = new DBSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();

Redis Session Storage

ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379?auth=secretpassword');
session_start();


Session Best Practices

  • Always call session_start() before output
  • Regenerate IDs after login/logout
  • Set proper cookie parameters:
    session_set_cookie_params([
        'lifetime' => 86400, // 1 day
        'path' => '/',
        'domain' => $_SERVER['HTTP_HOST'],
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Strict'
    ]);
  • Never store sensitive data in sessions (like passwords)
  • Implement idle timeout (e.g., 30 minutes)
  • Validate session consistency (IP, User-Agent)
  • Use custom session handlers for large-scale apps
  • Monitor session activity for anomalies


Common Session Vulnerabilities & Fixes

1. Session Fixation

Attack: Attacker sets a known session ID

Fix: Always regenerate ID after authentication

session_regenerate_id(true);

2. Session Hijacking

Attack: Stealing session IDs via XSS

Fix: Use HttpOnly and Secure flags

session.cookie_httponly = 1
session.cookie_secure = 1

3. Session Prediction

Attack: Guessing valid session IDs

Fix: Use strong session IDs

session.sid_length = 128
session.sid_bits_per_character = 6

4. Session Data Tampering

Attack: Modifying serialized session data

Fix: Store in database with validation



Complete Secure Session Example

<?php
// Initialize secure session
function secure_session_start() {
    $session_name = 'SECURE_SESSION';
    $secure = true; // HTTPS only
    $httponly = true; // No JS access
    
    // Force cookies to be secure
    ini_set('session.cookie_secure', 1);
    ini_set('session.cookie_httponly', 1);
    ini_set('session.use_strict_mode', 1);
    
    // Set custom cookie params
    session_set_cookie_params([
        'lifetime' => 86400,
        'path' => '/',
        'domain' => $_SERVER['HTTP_HOST'],
        'secure' => $secure,
        'httponly' => $httponly,
        'samesite' => 'Strict'
    ]);
    
    session_name($session_name);
    session_start();
    
    // Regenerate ID if too old
    if (!isset($_SESSION['created'])) {
        session_regenerate_id(true);
        $_SESSION['created'] = time();
    } elseif (time() - $_SESSION['created'] > 1800) {
        session_regenerate_id(true);
        $_SESSION['created'] = time();
    }
    
    // Validate against hijacking
    if (!isset($_SESSION['ip'])) {
        $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
    }
    
    if ($_SESSION['ip'] !== $_SERVER['REMOTE_ADDR']) {
        session_unset();
        session_destroy();
        die("Security violation");
    }
}

secure_session_start();
?>