This vulnerability occurs when an application sets a sensitive cookie (like a session identifier, authentication token, or CSRF token) over an HTTPS connection but fails to set the Secure attribute on that cookie. Without the Secure flag, the browser might transmit the cookie over subsequent unencrypted HTTP requests to the same domain (e.g., if the user manually types http:// or clicks an http:// link). An attacker eavesdropping on the network (e.g., on public Wi-Fi) could then intercept this cookie and potentially hijack the user’s session. 🍪🔓➡️👂
This is almost always a configuration issue. Modern web frameworks typically provide settings to enforce the Secure flag globally for session cookies, especially in production environments (often linked to HTTPS redirection settings). The vulnerability arises when these settings are disabled, misconfigured, or when developers set custom sensitive cookies manually without including the Secure flag.Remediation:
Enforce HTTPS: Ensure your entire application runs exclusively over HTTPS (see CWE-319).
Configure Secure Cookies: Set the framework’s configuration option to always add the Secure flag to session cookies in production.
Set Flag on Custom Cookies: When creating custom cookies containing sensitive data, explicitly add the Secure attribute.
# settings.py (Production)# DANGEROUS: Session cookie will be sent over HTTP if user visits an HTTP URL.SESSION_COOKIE_SECURE = False# Also dangerous for CSRF token:CSRF_COOKIE_SECURE = False# Even if SECURE_SSL_REDIRECT = True, a misconfiguration or temporary# availability over HTTP could leak the cookie.
Vulnerable Scenario 2: Flask Custom Cookie without secure=True
# app.py (Flask)from flask import make_response@app.route('/set-auth-token')def set_auth_token(): token = generate_sensitive_token() # Assume this generates a token response = make_response("Token Set") # DANGEROUS: Secure flag is missing. Cookie will be sent over HTTP. response.set_cookie('auth_token', token, httponly=True, samesite='Strict') return response
# settings.py (Django Production - Secure)# SECURE: Ensure cookies are only sent over HTTPS.SESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = True# Assumes HTTPS is enforced via SECURE_SSL_REDIRECT = True or proxy config.
# app.py (Flask - Secure Custom Cookie)from flask import make_response, request@app.route('/set-auth-token-secure')def set_auth_token_secure(): token = generate_sensitive_token() response = make_response("Token Set") # SECURE: Added secure=True. Flag only effective if site is accessed via HTTPS. # Use request.is_secure or config to determine if secure should be True. is_https = request.is_secure or request.headers.get('X-Forwarded-Proto', 'http') == 'https' response.set_cookie('auth_token', token, httponly=True, samesite='Strict', secure=is_https) # Set secure based on connection return response
Access your site over HTTPS. Use browser developer tools (Application/Storage tab) to inspect the session cookie (sessionid, csrftoken) and any custom sensitive cookies. Verify that the “Secure” column/flag is checked/true. Try accessing an HTTP version of a page (if HTTPS redirection isn’t perfectly enforced) and see if the cookie is still sent (it shouldn’t be if Secure is set).
# application-production.properties (Secure)# SECURE: Ensure JSESSIONID is only sent over HTTPS.server.servlet.session.cookie.secure=true# Also recommended:server.servlet.session.cookie.http-only=trueserver.servlet.session.cookie.same-site=Strict
// controller/AuthController.java (Secure Custom Cookie)@PostMapping("/issue-token-secure")public String issueTokenSecure(HttpServletResponse response, HttpServletRequest request) { String token = generateSensitiveToken(); Cookie tokenCookie = new Cookie("authToken", token); tokenCookie.setHttpOnly(true); tokenCookie.setPath("/"); // SECURE: Set the Secure flag. tokenCookie.setSecure(request.isSecure()); // Base on current request or assume true if HTTPS enforced response.addCookie(tokenCookie); return "tokenIssued";}
Access the site over HTTPS. Use browser developer tools to inspect the JSESSIONID cookie and any custom sensitive cookies. Verify the “Secure” flag is set. Use a proxy (like Burp Suite) to see if cookies are sent during subsequent HTTP requests (they shouldn’t be if flagged Secure).
Vulnerable Scenario 1: CookieSecurePolicy.None or SameAsRequest
The cookie policy doesn’t enforce ‘Always’. SameAsRequest is only secure if all traffic is guaranteed to be HTTPS.
// Startup.cs (ConfigureServices)services.AddSession(options =>{ options.Cookie.Name = ".MyApp.Session"; options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; // DANGEROUS: Allows cookie over HTTP if the request establishing it was HTTP, // or always allows if set to None. options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Or .None});services.ConfigureApplicationCookie(options => // For Identity cookies{ // DANGEROUS: Similar issue for authentication cookie. options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Or .None});
Vulnerable Scenario 2: Manual Cookie Creation without Secure=true
// Controllers/SettingsController.cspublic IActionResult SavePreference(string prefValue){ // DANGEROUS: Secure flag not set to true. Response.Cookies.Append("userPref", prefValue, new CookieOptions { HttpOnly = true, SameSite = SameSiteMode.Strict // Secure = true, // This is missing }); return Ok();}
Set CookieSecurePolicy.Always for session and authentication cookies in Startup.cs for production environments. When manually appending cookies via Response.Cookies.Append, set CookieOptions.Secure = true.
Access the site over HTTPS. Use browser developer tools to inspect session (.MyApp.Session) and authentication (.AspNetCore.Identity.Application) cookies. Verify the “Secure” flag is set. Check any custom cookies set by the application.
Controlled by the session.cookie_secure directive in php.ini or ini_set(), and the secure parameter in setcookie(). Laravel controls this via config/session.php.
Vulnerable Scenario 1: session.cookie_secure = Off
; php.ini (Production Server)session.use_cookies = 1session.use_only_cookies = 1; DANGEROUS: Allows PHPSESSID cookie to be sent over HTTP.session.cookie_secure = Offsession.cookie_httponly = On ; HttpOnly is good, but Secure is missingsession.cookie_samesite = Lax ; SameSite is good, but Secure is missing
Access the site over HTTPS. Use browser developer tools to inspect the PHPSESSID (or custom session name), laravel_session, and any custom sensitive cookies. Verify the “Secure” flag is set. Use phpinfo() (if accessible, shouldn’t be in prod) to check session.cookie_secure.
express-session: Set cookie.secure to true in production environments. Often done conditionally: secure: process.env.NODE_ENV === 'production'. Ensure your proxy correctly sets X-Forwarded-Proto and enable trust proxy in Express if behind TLS termination.
res.cookie: Explicitly add secure: true (or the conditional check) to the options object for sensitive cookies.
Access the site over HTTPS in a production-like environment (NODE_ENV=production). Inspect the session cookie (connect.sid or custom name) and other sensitive cookies using browser developer tools. Verify the “Secure” flag is set.
Controlled by config.force_ssl (which forces secure cookies globally) or the :secure option in config/initializers/session_store.rb or when setting cookies manually.
If HTTPS is enforced only at the proxy level and config.force_ssl is false, cookies might not get the Secure flag automatically.
# config/environments/production.rbRails.application.configure do # DANGEROUS: If set to false, Rails might not add the Secure flag by default, # relying on developers to add it manually or configure session_store. config.force_ssl = false # ...end# config/initializers/session_store.rb# If :secure option is missing or false, cookie won't be secureRails.application.config.session_store :cookie_store, key: '_my_app_session'
config.force_ssl = true: This is the easiest and most robust way in Rails production environments. It enforces HTTPS, HSTS, and sets the Secure flag on cookies automatically.
Manual Cookies: If not using force_ssl, always add secure: Rails.env.production? (or just secure: true if always HTTPS) to the options hash when setting sensitive cookies manually.
Access the site over HTTPS. Use browser developer tools to inspect the session cookie (_my_app_session) and any custom sensitive cookies. Verify the “Secure” flag is set. Check config/environments/production.rb for config.force_ssl = true or review session_store.rb and manual cookie setting code for the :secure flag.