Skip to content

CWE-614: Sensitive Cookie Without 'Secure' Flag - PHP

Overview

The Secure attribute on an HTTP cookie instructs the browser to only transmit that cookie over encrypted HTTPS connections. Without this attribute, the browser also sends the cookie on plaintext HTTP requests. If a user is directed to an HTTP version of a page - by following an HTTP link, via a DNS rebinding attack, or because the site does not enforce HTTPS - the session cookie travels in the clear and can be intercepted by any observer on the network path.

In PHP, setcookie() does not set the Secure flag by default. The same applies to PHP session cookies: session_start() uses whatever settings are in php.ini, which typically does not have session.cookie_secure = 1 enabled unless explicitly configured.

Primary Defence: Pass 'secure' => true in the options array to every setcookie() call. Configure session.cookie_secure = 1 in php.ini (or via session_set_cookie_params() before session_start()). Combine with httponly and samesite for defence-in-depth.

Common Vulnerable Patterns

setcookie() Without Secure Flag

<?php
// VULNERABLE - cookie sent over HTTP as well as HTTPS
setcookie('auth_token', $tokenValue, time() + 3600, '/');

// Legacy five-parameter form - easy to miss security parameters
setcookie('session_pref', $preference, time() + 86400, '/', '', false, true);
//                                                               ^------- secure (FALSE!)

Why this is vulnerable:

  • Omitting the secure parameter (which defaults to false) means the browser will include this cookie in HTTP requests. An attacker on the same network can capture the auth_token cookie and impersonate the user.
<?php
// VULNERABLE - session cookie not configured as Secure
session_start(); // Uses php.ini defaults; session.cookie_secure defaults to 0

// Later the session ID cookie is sent on both HTTP and HTTPS requests
$_SESSION['user_id'] = $userId;

Why this is vulnerable:

  • The PHP default session.cookie_secure = 0 sends the PHPSESSID cookie over HTTP. A compromised network path exposes the session identifier, allowing session hijacking.

setcookie() Using Positional Parameters

<?php
// VULNERABLE - positional form makes it easy to accidentally set secure=false
setcookie(
    'remember_me',  // name
    $token,         // value
    time() + 2592000, // expires
    '/',            // path
    '',             // domain
    false,          // VULNERABLE: secure = false
    true            // httponly = true
);

Why this is vulnerable:

  • The positional (pre-PHP 7.3) syntax places secure at parameter position 6. Accidentally passing false or omitting it means the cookie is not protected.

Secure Patterns

setcookie() with Options Array (PHP 7.3+)

<?php
// SECURE: options array makes each attribute explicit and readable
setcookie('auth_token', $tokenValue, [
    'expires'  => time() + 3600,
    'path'     => '/',
    'domain'   => '',        // current host only
    'secure'   => true,      // HTTPS only
    'httponly' => true,      // not accessible via JavaScript
    'samesite' => 'Strict',  // not sent on cross-site requests (CSRF protection)
]);

Why this works:

  • The options array form (PHP 7.3+) makes every cookie attribute explicitly named, eliminating positional mistakes. Setting 'secure' => true ensures the browser only sends the cookie over HTTPS.
<?php
// SECURE: configure session cookie before session_start()
session_set_cookie_params([
    'lifetime' => 0,          // session cookie (deleted on browser close)
    'path'     => '/',
    'domain'   => '',
    'secure'   => true,       // HTTPS only
    'httponly' => true,
    'samesite' => 'Strict',
]);
session_start();

// Regenerate session ID after authentication to prevent fixation
session_regenerate_id(true);
$_SESSION['user_id'] = $authenticatedUserId;

Why this works:

  • Calling session_set_cookie_params() before session_start() configures the PHPSESSID cookie with the Secure flag. session_regenerate_id(true) prevents session fixation attacks by replacing the session ID after login.
; php.ini - enforce secure cookie settings at the server level
session.cookie_secure   = 1    ; Session cookies HTTPS only
session.cookie_httponly = 1    ; Session cookies not accessible via JS
session.cookie_samesite = Strict  ; Prevent CSRF via cross-site requests

; Optional: restrict session cookies to HTTPS scheme in the cookie name
session.name = __Secure-PHPSESSID  ; Browser-enforced Secure flag requirement

Why this works:

  • php.ini settings apply to all PHP scripts on the server, providing a baseline security configuration. The __Secure- cookie name prefix instructs the browser to reject the cookie if it arrives without the Secure attribute, adding a redundant enforcement mechanism.

Remediation Steps

Locate the Finding

  • Source: Any setcookie() call, session_start() without prior session_set_cookie_params()
  • Sink: Missing 'secure' => true in setcookie() options; session.cookie_secure = 0 in php.ini

Apply the Fix

  • PRIORITY 1: Update all setcookie() calls to use the options array with 'secure' => true
  • PRIORITY 2: Add session_set_cookie_params(['secure' => true, 'httponly' => true, 'samesite' => 'Strict']) before every session_start() call
  • PRIORITY 3: Set session.cookie_secure = 1 in php.ini as a server-wide baseline

Verify the Fix

  • Use browser developer tools -> Application -> Cookies; confirm the Secure column is checked for all sensitive cookies
  • Navigate to the HTTP version of the app and confirm the session cookie is not present in the request
  • Rescan with the security scanner to confirm the finding is resolved

Check for Similar Issues

Search for: setcookie(, session_start(, review each call for secure => true

Testing

  • Normal input: sign in over HTTPS and confirm session, authentication, and preference cookies are set and accepted.
  • Boundary input: test local development, staging behind a proxy, and redirects from HTTP to HTTPS to ensure cookie behavior is intentional.
  • Malicious input: make an HTTP request to the same host and verify sensitive cookies are absent from the request headers.

Additional Resources