Sensitive Cookie in HTTPS Session Without 'Secure' Attribute
Description
Sensitive Cookie in HTTPS Session Without 'Secure' Attribute occurs when a cookie containing sensitive data (such as session tokens, authentication tokens, or user credentials) is set without the Secure flag. Without this flag, the browser will send the cookie over unencrypted HTTP connections, even if the cookie was originally set over HTTPS. Attackers performing man-in-the-middle attacks can intercept these cookies when users access any HTTP resource, even on different sites.
Risk
Missing Secure flag on sensitive cookies enables cookie theft through network interception. Even on sites that enforce HTTPS, browsers will send non-secure cookies over HTTP if the user visits any HTTP resource (including images or redirects). Attackers on shared networks (WiFi hotspots, corporate networks) can easily capture these cookies. Session hijacking through cookie theft remains one of the most common web attacks. The attack requires minimal skill with tools like Wireshark or Firesheep.
Solution
Always set the Secure flag on cookies containing sensitive data. Use HTTPS for all application resources. Implement HTTP Strict Transport Security (HSTS) to prevent HTTP downgrade attacks. Set appropriate cookie attributes: Secure, HttpOnly, SameSite. Use framework defaults that apply security attributes automatically. Audit cookie settings regularly. Consider using __Host- or __Secure- cookie prefixes for additional protection.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Session Theft Session cookies transmitted over HTTP can be intercepted, enabling session hijacking. |
| Access Control | Scope: Account Takeover Stolen authentication cookies allow attackers to impersonate users. |
| Integrity | Scope: Unauthorized Actions Attackers with stolen sessions can perform actions as the victim user. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: Session cookie without Secure flag
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'secret'
# No secure cookie configuration!
@app.route('/login')
def login():
session['user_id'] = user.id
# Cookie sent without Secure flag
return redirect('/dashboard')
# VULNERABLE: Manual cookie without Secure
from flask import make_response
@app.route('/set-token')
def set_token():
response = make_response('Token set')
# Missing Secure flag!
response.set_cookie('auth_token', token, httponly=True)
return response
// VULNERABLE: Servlet cookie without Secure
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Authenticate user...
Cookie sessionCookie = new Cookie("SESSIONID", sessionId);
sessionCookie.setHttpOnly(true);
// Missing: sessionCookie.setSecure(true);
response.addCookie(sessionCookie);
response.sendRedirect("/dashboard");
}
}
// VULNERABLE: Spring session without secure cookies
@Configuration
public class SessionConfig {
// No cookie configuration - defaults may not be secure
}
// VULNERABLE: Express session without secure cookie
const session = require('express-session');
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true
// Missing: secure: true
}
}));
// VULNERABLE: Manual cookie without secure flag
app.get('/set-cookie', (req, res) => {
res.cookie('authToken', token, {
httpOnly: true
// Missing: secure: true
});
res.send('Cookie set');
});
// VULNERABLE: PHP session without secure flag
<?php
session_start(); // Uses default settings
$_SESSION['user_id'] = $user_id;
// VULNERABLE: setcookie without Secure
setcookie('auth_token', $token, [
'httponly' => true,
// 'secure' => true is missing!
'path' => '/'
]);
?>
Fixed Code
# SAFE: Flask with secure cookie configuration
from flask import Flask, session
app = Flask(__name__)
app.secret_key = os.urandom(32)
# Configure secure cookies
app.config.update(
SESSION_COOKIE_SECURE=True, # Secure flag
SESSION_COOKIE_HTTPONLY=True, # HttpOnly flag
SESSION_COOKIE_SAMESITE='Lax', # SameSite attribute
SESSION_COOKIE_NAME='__Host-session' # Cookie prefix for extra security
)
@app.route('/login')
def login():
session['user_id'] = user.id
# Cookie now has Secure flag
return redirect('/dashboard')
# SAFE: Manual cookie with all security attributes
@app.route('/set-token')
def set_token():
response = make_response('Token set')
response.set_cookie(
'auth_token',
token,
secure=True, # Only sent over HTTPS
httponly=True, # Not accessible via JavaScript
samesite='Strict', # Not sent in cross-site requests
max_age=3600, # 1 hour expiry
path='/'
)
return response
# SAFE: Force HTTPS and HSTS
@app.before_request
def enforce_https():
if not request.is_secure and not app.debug:
return redirect(request.url.replace('http://', 'https://'), code=301)
@app.after_request
def add_security_headers(response):
# HSTS header - force HTTPS for 1 year
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains; preload'
return response
// SAFE: Servlet with secure cookies
@WebServlet("/login")
public class SecureLoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Authenticate user...
Cookie sessionCookie = new Cookie("SESSIONID", sessionId);
sessionCookie.setSecure(true); // Only HTTPS
sessionCookie.setHttpOnly(true); // No JavaScript access
sessionCookie.setPath("/");
sessionCookie.setMaxAge(3600); // 1 hour
// SameSite requires Servlet 4.0+ or header manipulation
response.setHeader("Set-Cookie",
sessionCookie.getName() + "=" + sessionCookie.getValue() +
"; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600");
response.sendRedirect("/dashboard");
}
}
// SAFE: Spring Security configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requiresChannel()
.anyRequest().requiresSecure() // Force HTTPS
.and()
.headers()
.httpStrictTransportSecurity()
.includeSubDomains(true)
.maxAgeInSeconds(31536000);
}
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("SESSION");
serializer.setUseSecureCookie(true); // Secure flag
serializer.setUseHttpOnlyCookie(true); // HttpOnly flag
serializer.setSameSite("Strict"); // SameSite attribute
serializer.setCookiePath("/");
return serializer;
}
}
// SAFE: Spring Boot application.properties
// server.servlet.session.cookie.secure=true
// server.servlet.session.cookie.http-only=true
// server.servlet.session.cookie.same-site=strict
// SAFE: Express with secure session cookies
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
name: '__Host-sessionId', // Cookie prefix
cookie: {
secure: true, // Only sent over HTTPS
httpOnly: true, // Not accessible via JavaScript
sameSite: 'strict', // Not sent in cross-site requests
maxAge: 3600000, // 1 hour
path: '/'
}
}));
// SAFE: Manual secure cookie
app.get('/set-cookie', (req, res) => {
res.cookie('authToken', token, {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 3600000,
path: '/'
});
res.send('Cookie set');
});
// SAFE: Force HTTPS with helmet
const helmet = require('helmet');
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
}));
// Redirect HTTP to HTTPS
app.use((req, res, next) => {
if (!req.secure && req.get('x-forwarded-proto') !== 'https') {
return res.redirect(301, `https://${req.get('host')}${req.url}`);
}
next();
});
// SAFE: PHP with secure session cookies
<?php
// Configure session before starting
ini_set('session.cookie_secure', 1); // Secure flag
ini_set('session.cookie_httponly', 1); // HttpOnly flag
ini_set('session.cookie_samesite', 'Strict'); // SameSite attribute
ini_set('session.use_only_cookies', 1); // Only use cookies
ini_set('session.use_strict_mode', 1); // Strict session mode
session_start();
$_SESSION['user_id'] = $user_id;
// SAFE: Manual cookie with all attributes
setcookie('auth_token', $token, [
'expires' => time() + 3600,
'path' => '/',
'secure' => true, // Only HTTPS
'httponly' => true, // No JavaScript
'samesite' => 'Strict' // No cross-site
]);
// Or using header for full control
header('Set-Cookie: auth_token=' . $token . '; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600');
?>
Exploited in the Wild
Firesheep (2010)
Firesheep was a Firefox extension that demonstrated the ease of hijacking sessions on unencrypted WiFi networks by capturing cookies without the Secure flag from major sites like Facebook and Twitter.
SSL Strip Attacks
Attackers use SSL stripping to downgrade HTTPS connections to HTTP, capturing cookies that lack the Secure flag even on sites that normally use HTTPS.
Public WiFi Session Hijacking
Numerous real-world attacks on public WiFi networks have exploited missing Secure flags to steal session cookies from banking, email, and social media sites.
Tools to test/exploit
-
Burp Suite — analyze cookie attributes.
-
OWASP ZAP — scan for insecure cookies.
-
Browser DevTools — inspect cookie settings.
-
Wireshark — capture unencrypted cookie traffic.
CVE Examples
-
CVE-2021-22893 — Pulse Secure cookie vulnerability.
-
CVE-2020-8945 — GPG cookie security issue.
-
CVE-2019-5736 — Container cookie exposure.
References
-
MITRE. "CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute." https://cwe.mitre.org/data/definitions/614.html
-
OWASP. "Secure Cookie Attribute." https://owasp.org/www-community/controls/SecureCookieAttribute