Skip to content

CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute

Overview

A sensitive cookie - a session ID, an authentication token - set without the Secure flag is sent on plain HTTP requests as well as HTTPS ones, where anyone on the network path can read it.

Relationship to Other CWEs

Report a finding here when the transport is already HTTPS and what is missing is a cookie attribute. Report it against the parent, CWE-319 (Cleartext Transmission of Sensitive Information), when sensitive data crosses the network in the clear for any other reason - adding Secure fixes nothing about an endpoint that still answers on http://.

The pages around it differ by what the missing protection is attached to:

  • CWE-614 (this page) - a sensitive cookie is issued without Secure, so the browser attaches it to any plaintext request to the same host
  • CWE-384 (Session Fixation) - the identifier inside the cookie rather than the attributes on it, where a session established before authentication stays valid after it. Setting Secure keeps that identifier off the wire and changes nothing about an identifier the attacker planted
  • CWE-522 (Insufficiently Protected Credentials) - how credentials and session tokens are protected anywhere in the application, of which a session cookie exposed in transit is one case; that page routes the cookie-attribute part of the fix here

MITRE records no children under this entry. The HttpOnly flag recommended below alongside Secure is a separate entry, CWE-1004, which MITRE files under CWE-732 rather than here and which has no page of its own in this repository.

OWASP Classification

A02:2025 - Security Misconfiguration

Risk

High: An intercepted session cookie can be replayed, giving the attacker the session it belongs to and whatever the account can reach.

Remediation Steps

Core Principle: Sensitive cookies must never travel over an unencrypted connection. The server declares that constraint with the cookie's attributes and the browser is the thing that honours it, withholding the cookie from any plaintext request - so the control is the attribute you set plus the transport you serve over, never a check written into your own client-side code.

Locate cookies missing the Secure flag

  • Start from the finding to see which cookies lack the flag, then look for the same gap in the rest of the cookie-setting code: response.set_cookie(), res.cookie(), Cookie.setValue()
  • Identify the sensitive ones: session IDs, authentication tokens, CSRF tokens, and any preference cookie that carries an internal identifier
  • Trace where cookies are created, particularly in authentication and session management

Set the Secure flag on all sensitive cookies (Primary Defense)

  • Set the flag wherever a cookie is issued: response.set_cookie('sessionid', value, secure=True). The browser then withholds the cookie from any plaintext request
  • Framework equivalents: Flask secure=True, Express secure: true, Spring cookie.setSecure(true)
  • Apply it to every sensitive cookie - session IDs, auth tokens, CSRF tokens, remember-me tokens - not only the one the scanner flagged
  • Use HSTS so that once the browser has seen the site over HTTPS, it uses HTTPS for every later connection
  • Consider a name prefix. Renaming the cookie to __Host-<name> (or __Secure-<name>) makes the browser reject it if Secure is missing, so a deployment that loses the flag fails visibly instead of issuing a plaintext cookie. Apply it only after confirming the flag reaches the wire - the browser silently discards a prefixed cookie without it, which presents as a login loop rather than as a cookie problem

Use HttpOnly and SameSite flags for defense in depth

  • Set HttpOnly so client-side JavaScript cannot read the cookie, which limits what an XSS bug can steal
  • Set SameSite=Strict or SameSite=Lax to mitigate CSRF. Choose per flow rather than defaulting to Strict - unlike Secure, the stricter value is not simply the safer one
  • Under SameSite=Strict the cookie is never sent on cross-site requests. Use it only where nothing legitimate navigates in from another site: a user arriving from an email link, a search result or an SSO redirect lands signed out, which reads as a session bug
  • Under SameSite=Lax the cookie is sent on top-level navigation and withheld from cross-site POSTs and subresource loads. OAuth and SSO callbacks need this, because the provider's redirect is itself a cross-site navigation
  • Do not force SameSite globally. A framework-wide "minimum SameSite" setting can rewrite a cookie that deliberately asked for Lax or None, breaking the flow that needed it. Forcing Secure globally is safe; forcing SameSite is not
  • SameSite=None is only accepted alongside Secure. Use SameSite=None; Secure only when a cookie must be sent in third-party contexts
  • A complete sensitive cookie has secure=True always, httponly=True unless a page script must read the value, and samesite set to whichever value the flow needs - 'Strict' or 'Lax' unless the cookie must be sent in third-party contexts
  • Omit Domain so the cookie is scoped to the exact host. Setting a parent domain (.example.com) exposes the cookie to every subdomain, including legacy applications, staging environments, and any subdomain an attacker can claim
  • Treat Path as tidiness, not a boundary. It limits which requests carry the cookie, but it isolates nothing from an attacker - script running anywhere on the origin can read a cookie from any path by loading that path in an iframe. Narrow it to reduce accidental exposure, never as the control that protects a cookie. The __Host- prefix requires Path=/, so the two cannot be combined
  • Keep cookies short-lived: session cookies expire on browser close, or set max_age for a limited lifetime
  • Regenerate session IDs periodically, and after any privilege change
  • Log session creation and authentication events
  • Alert on suspicious cookie usage, such as duplicate sessions from multiple IPs
  • Check cookie attributes in code review, and with a security headers scanner against the running site
  • Secure is set: inspect Set-Cookie headers in browser DevTools or Burp Suite
  • The cookie is not transmitted on a plain HTTP request to the same host
  • HttpOnly is set: the cookie does not appear in document.cookie
  • Cross-site requests behave as the chosen Strict, Lax, or None; Secure value dictates
  • The application still works over HTTPS with the new attributes in place
  • Re-scan with the security scanner to confirm the issue is resolved

Common Vulnerable Patterns

  • Setting cookies without the Secure flag
  • Allowing cookies to be sent over HTTP
  • Failing to use HttpOnly or SameSite attributes
// VULNERABLE - pseudo-code
set_cookie("sessionid", value)   // no Secure, HttpOnly, or SameSite attributes

Why this is vulnerable: Without Secure, the browser sends the cookie over HTTP as well as HTTPS. Anyone on the same network - public WiFi, a compromised router - can read it by packet sniffing or by intercepting the connection, then replay the session cookie to take over the account.

Secure Patterns

// SECURE - pseudo-code
set_cookie("sessionid", value,
    secure=true,       // only sent over HTTPS
    http_only=true,     // not accessible to JavaScript
    same_site="Strict") // not sent in cross-site requests

Why this works:

  • Secure keeps the cookie off unencrypted connections, so there is no plaintext request for a network attacker to read it from
  • HttpOnly blocks JavaScript access to the cookie, reducing XSS-based session theft but not fixing XSS itself
  • SameSite='Strict' withholds the cookie from cross-site requests, reducing CSRF exposure as defense in depth. It is shown here because this example's flow starts on the site's own pages; Lax is the correct value wherever a cross-site navigation must carry the cookie

Common Pitfalls

  • Assuming the framework's global cookie setting covers every cookie: Turning on a framework-wide "secure cookies" setting (e.g., a session cookie flag) and treating the finding as resolved - that setting typically only covers the framework's own session/CSRF cookie; a separate analytics or tracking cookie, a cookie set by client-side JavaScript (document.cookie = ...), or any cookie your own code sets directly is issued independently and needs the flag applied at that specific call.
  • Securing only the login cookie: Adding Secure to the authentication cookie while a "remember me" token, a CSRF cookie, or a preference cookie carrying an internal identifier is set elsewhere without it - there is no inheritance between cookies; each Set-Cookie needs its own attributes.
  • Relying on SameSite or HSTS as a substitute: SameSite controls whether a cookie is sent on cross-site requests, not whether it can travel over plaintext HTTP; HSTS only takes effect after the browser has already visited the site once over HTTPS (or the domain is preloaded), so a cookie without Secure can still leak on that first HTTP request or during a downgrade.
  • Deriving "secure" from the wrong signal behind a reverse proxy: Setting the flag conditionally based on the application's own view of the request scheme (e.g., checking if the connection it received is HTTPS) when a load balancer or reverse proxy terminates TLS in front of it - the app sees a plain HTTP connection internally and omits Secure, even though the browser-facing connection was HTTPS, unless the proxy header is explicitly forwarded and trusted.

Language-Specific Guidance

Concrete APIs and framework patterns for each language:

  • C#/.NET - ASP.NET Core cookie options with Secure, HttpOnly, SameSite
  • Go - net/http, Gin, Echo cookie security with Secure, HttpOnly, SameSite flags
  • Java - Servlet/Spring Boot/JAX-RS cookie security with setSecure(true), setHttpOnly(true)
  • JavaScript/Node.js - Express/Fastify/Next.js cookie security with secure: true, httpOnly: true, sameSite: 'strict'
  • PHP - session.cookie_secure, session_set_cookie_params(), setcookie() flags
  • Python - Flask/Django/FastAPI cookie security with secure=True, httponly=True, samesite='Strict'

Additional Resources