CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute - 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, by typing the bare hostname into the address bar, or because an on-path attacker injected a redirect to http:// - 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, and session.cookie_secure defaults to 0.
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()). Add 'httponly' => true for any cookie no page script needs to read. SameSite is chosen per flow, not set to Strict by default: use Strict only where nothing legitimate navigates in from another site, and Lax for OAuth/SSO callbacks and ordinary inbound links.
Common Vulnerable Patterns
setcookie() Without Secure Flag
<?php
// VULNERABLE - cookie sent over HTTP as well as HTTPS
setcookie('auth_token', $tokenValue, time() + 3600, '/');
// Legacy positional form - secure is the sixth argument, and nothing names it
setcookie('session_pref', $preference, time() + 86400, '/', '', false, true);
// ^--- secure (FALSE!)
Why this is vulnerable:
- Omitting the
secureparameter (which defaults tofalse) means the browser will include this cookie in HTTP requests. An attacker on the same network can capture theauth_tokencookie and impersonate the user.
session_start() Without Secure Cookie Parameters
<?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 = 0sends thePHPSESSIDcookie 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
secureat parameter position 6. Accidentally passingfalseor omitting it means the cookie is sent over plaintext HTTP as well as HTTPS.
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' => trueensures the browser only sends the cookie over HTTPS.
Session Cookie Security
<?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()beforesession_start()configures thePHPSESSIDcookie with theSecureflag.session_regenerate_id(true)prevents session fixation attacks by replacing the session ID after login.
php.ini Configuration (Recommended for All Sites)
; 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: make the browser enforce the flag as well as PHP setting it
session.name = __Secure-PHPSESSID ; only accepted if Secure is actually present
Why this works:
php.inisettings apply to every PHP script on the server, so the flag holds even for code that never callssession_set_cookie_params(). The__Secure-cookie name prefix instructs the browser to reject the cookie if it arrives without theSecureattribute, so a deployment that quietly losessession.cookie_securefails loudly instead of silently issuing a plaintext session cookie.
Change the name only once you have confirmed session.cookie_secure is actually in force on the pool serving requests. PHP accepts the prefixed name regardless: with the flag off it will happily emit Set-Cookie: __Secure-PHPSESSID=...; path=/ with no Secure, and every browser then discards it. The application does not error - it simply never has a session, which reads as a login loop rather than as a cookie problem. That is the same trade the __Host- prefix makes: it converts a silent weakness into a visible outage, which is what you want, but only after the flag is verified on the wire.
Considerations
Strict is a choice, not the safe default. The examples on this page use
Strict because they issue cookies for flows that begin on the application's
own pages. Strict withholds the cookie on every cross-site request,
including a user arriving from an email link, a search result, a partner portal
or an identity provider's redirect - they land signed out, then appear signed
in after any same-site click. It reads as a session bug and it is a
configuration choice. Lax still withholds the cookie from cross-site POSTs
and subresource loads, which is the CSRF-relevant part, while sending it on
top-level navigations. Use Strict only where nothing legitimate navigates in
from elsewhere, and treat OAuth and SSO callbacks as requiring Lax outright,
because the provider's redirect is a cross-site navigation. None of this
changes Secure, which is what this finding is about and which is required
either way.
session.cookie_samesite = Strict in php.ini applies that choice to every
site served by the pool, so a single OAuth-based application among them is
enough to make it the wrong setting there. Set session.cookie_secure = 1
globally - it has no per-application exception - and leave samesite to
session_set_cookie_params() in the applications that need something other
than the default.
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.
Common Pitfalls
- Setting
session.cookie_secure = 1in onephp.iniwhile the request is actually served by a different SAPI or pool configuration (CLI vs. PHP-FPM pool, or a.user.ini/php_valueoverride at the vhost level) that doesn't carry the same setting - PHP configuration is layered and per-SAPI, so a value confirmed in one context doesn't guarantee it applies to the process handling real requests. - Calling
session_set_cookie_params()with'secure' => trueaftersession_start()has already run - the parameters only take effect for the next session start, so a session already begun under the previous (insecure) defaults keeps its original cookie attributes until it's regenerated. - Using the legacy positional
setcookie()signature and counting arguments to reach thesecureparameter - the pre-7.3 positional form accepts a shifted argument list without any type or count error, so an unrelated refactor elsewhere in the call can silently movesecureinto the wrong position.