0

Timing attacks and hash_equals() for constant-time comparison

Advanced5 min read·php-16-005
securityinterview

Concept

Sessions are the standard mechanism for maintaining state across HTTP requests. PHP sessions store data server-side, identified by a session ID stored in a cookie. Misconfigured sessions lead to session fixation, session hijacking, and session theft.

Session fixation: Attacker gives victim a known session ID (e.g., via URL parameter). After victim logs in, attacker uses the same ID to take over the authenticated session. Fix: call session_regenerate_id(true) on every authentication state change.

Session hijacking: Attacker obtains a valid session cookie (via XSS, network sniffing, server logs). Fix: HTTPS (prevents sniffing), HttpOnly flag (prevents XSS cookie theft), short session lifetimes, IP/user-agent binding (optional — breaks mobile users).

Critical session security settings:

  • session.cookie_httponly = 1: JavaScript cannot access the session cookie — blocks XSS-based cookie theft.
  • session.cookie_secure = 1: Cookie only sent over HTTPS — prevents transmission over HTTP.
  • session.cookie_samesite = Lax (PHP 7.3+): Prevents CSRF via cookie.
  • session.use_only_cookies = 1: Prevents session ID from being passed in URL — prevents log exposure.
  • session.use_strict_mode = 1: PHP rejects session IDs not generated by the server — prevents session fixation.
  • session.gc_maxlifetime = 1440: Sessions expire after 24 minutes of inactivity.

Laravel session: Configured in config/session.php. Default driver is file. Production: use redis or database. Session data is encrypted at rest (APP_KEY used for encryption).

Code Example

php
<?php
// Secure session configuration
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1');      // requires HTTPS
ini_set('session.cookie_samesite', 'Lax');
ini_set('session.use_only_cookies', '1');
ini_set('session.use_strict_mode', '1');
ini_set('session.gc_maxlifetime', '1440');

session_start();

// Prevent session fixation — regenerate ID on login
function loginUser(array $credentials): bool
{
    $user = authenticate($credentials);
    if (!$user) return false;

    // CRITICAL: regenerate session ID after authentication
    // true = delete the old session data
    session_regenerate_id(true);

    $_SESSION['user_id'] = $user['id'];
    $_SESSION['authenticated'] = true;
    $_SESSION['login_time'] = time();

    return true;
}

// Session timeout enforcement
function checkSessionTimeout(int $maxIdleSeconds = 1800): void
{
    if (isset($_SESSION['last_activity'])) {
        if (time() - $_SESSION['last_activity'] > $maxIdleSeconds) {
            session_unset();
            session_destroy();
            // Redirect to login
            header('Location: /login?reason=timeout');
            exit;
        }
    }
    $_SESSION['last_activity'] = time();
}

// Logout — destroy everything
function logout(): void
{
    session_unset();
    session_destroy();
    // Clear the cookie
    setcookie(session_name(), '', [
        'expires'  => time() - 3600,
        'path'     => '/',
        'secure'   => true,
        'httponly' => true,
        'samesite' => 'Lax',
    ]);
    session_regenerate_id(true);
}

// Laravel session config (config/session.php)
// 'driver'          => env('SESSION_DRIVER', 'redis'),
// 'lifetime'        => 120,  // minutes
// 'expire_on_close' => false,
// 'encrypt'         => true, // encrypt session data
// 'secure'          => true, // HTTPS only
// 'http_only'       => true, // no JS access
// 'same_site'       => 'lax',