Insecure Cookies
What This Means
Insecure cookies are cookies that lack proper security attributes (Secure, HttpOnly, SameSite), making them vulnerable to interception, theft, and misuse. These missing flags expose your users to serious security risks including session hijacking, cross-site scripting (XSS) attacks, and cross-site request forgery (CSRF).
Cookie Security Attributes
Secure Flag:
- Cookie only sent over HTTPS connections
- Prevents interception over HTTP
- Blocks man-in-the-middle attacks
- Required for sensitive data
HttpOnly Flag:
- Cookie not accessible via JavaScript
- Prevents XSS attacks from stealing cookies
- Blocks
document.cookieaccess - Protects session tokens
SameSite Attribute:
- Controls cross-site cookie sending
- Prevents CSRF attacks
- Three values:
Strict,Lax,None - Required for modern browsers
Impact on Your Business
Security Vulnerabilities:
- Session hijacking - Attackers steal user sessions, impersonate users
- Account takeover - Stolen authentication cookies = full account access
- Data theft - XSS scripts read sensitive cookie data
- CSRF attacks - Malicious sites perform actions as logged-in users
- Man-in-the-middle - Cookies transmitted over HTTP intercepted
Business Impact:
- Users' accounts compromised
- Financial transactions hijacked
- Regulatory violations (GDPR, PCI DSS)
- Legal liability for data breaches
- Reputation damage and lost trust
- Compliance audit failures
Common Attack Scenarios:
- Public WiFi attacker intercepts HTTP cookie
- XSS script steals session cookie via
document.cookie - Malicious site triggers CSRF using user's cookies
- Mixed content allows HTTP cookie transmission
- Subdomain takeover reads parent cookies
How to Diagnose
Method 1: Browser DevTools (Primary)
- Open your website
- Open DevTools (
F12) - Navigate to Application tab (Chrome) or Storage tab (Firefox)
- Expand Cookies in left sidebar
- Click your domain
- Review cookie list
What to Look For:
| Cookie Name | Secure | HttpOnly | SameSite | Risk Level |
|---|---|---|---|---|
| session_id | β No | β No | β None | π΄ Critical |
| auth_token | β Yes | β No | β οΈ None | π‘ High |
| user_prefs | β No | β Yes | β Lax | π‘ Medium |
| analytics | β Yes | β Yes | β Lax | β Secure |
Red Flags:
- Session/auth cookies without
Secure - Session/auth cookies without
HttpOnly - Any cookie with
SameSite=NonewithoutSecure - Cookies over HTTP (not HTTPS)
Method 2: Network Tab Inspection
- Open DevTools β Network tab
- Reload the page
- Click on the main document request
- Navigate to Headers section
- Scroll to Response Headers
- Find
Set-Cookieheaders
What to Look For:
β BAD:
Set-Cookie: session=abc123; Path=/; Domain=.example.com
β οΈ BETTER:
Set-Cookie: session=abc123; Secure; Path=/; Domain=.example.com
β
GOOD:
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/; Domain=.example.com
β
BEST:
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict; Path=/; Domain=.example.com; Max-Age=3600
Method 3: Security Headers Checker
- Visit SecurityHeaders.com
- Enter your domain
- Review cookie warnings
- Check recommendations
What to Look For:
- Cookie security warnings
- Missing security attributes
- Recommendations for improvement
- Grade impact from insecure cookies
Method 4: Browser Console Check
Run this JavaScript in browser console:
// Get all cookies and analyze security
const cookies = document.cookie.split(';').map(c => c.trim());
console.log('Accessible cookies (potential XSS risk):', cookies);
// Note: HttpOnly cookies won't appear here (which is good for security)
// Secure and SameSite attributes not visible via document.cookie
// Check if site is HTTPS
console.log('Site using HTTPS:', window.location.protocol === 'https:');
// Warn about accessible cookies on HTTPS
if (cookies.length > 0 && cookies[0] !== '') {
console.warn('β οΈ These cookies are accessible via JavaScript (missing HttpOnly):');
cookies.forEach(c => console.warn(' -', c.split('=')[0]));
}
Method 5: OWASP ZAP or Burp Suite
Automated Security Testing:
- Install OWASP ZAP
- Configure browser to use ZAP proxy
- Browse your website
- Review Alerts tab
- Check for cookie security warnings
What to Look For:
- "Cookie No HttpOnly Flag"
- "Cookie Without Secure Flag"
- "Cookie Without SameSite Attribute"
- Severity ratings (High, Medium, Low)
Method 6: curl Command
# Check Set-Cookie headers
curl -I https://example.com
# Look for Set-Cookie headers
# Example output:
Set-Cookie: session=abc123; Path=/
Set-Cookie: user=john; Secure; HttpOnly; SameSite=Lax
# Analyze each cookie for missing flags
General Fixes
Fix 1: Add All Security Flags (Recommended)
Best practice configuration:
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600
Breakdown:
Secure- Only sent over HTTPSHttpOnly- Not accessible via JavaScriptSameSite=Strict- Never sent with cross-site requestsPath=/- Available for entire domainMax-Age=3600- Expires in 1 hour
Fix 2: Server-Side Cookie Configuration
Node.js/Express:
const express = require('express');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const app = express();
// Configure session with secure cookies
app.use(session({
secret: 'your-secret-key',
name: 'sessionId',
cookie: {
secure: true, // Require HTTPS
httpOnly: true, // Prevent JS access
sameSite: 'strict', // CSRF protection
maxAge: 3600000, // 1 hour in milliseconds
domain: '.example.com' // Available to subdomains
},
resave: false,
saveUninitialized: false
}));
// Set individual cookies
app.get('/login', (req, res) => {
res.cookie('authToken', 'token123', {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 86400000 // 24 hours
});
res.send('Logged in');
});
PHP:
<?php
// Set cookie with all security flags
setcookie(
'session', // name
'abc123', // value
[
'expires' => time() + 3600,
'path' => '/',
'domain' => '.example.com',
'secure' => true, // HTTPS only
'httponly' => true, // No JS access
'samesite' => 'Strict' // CSRF protection
]
);
// Session cookie configuration
ini_set('session.cookie_secure', '1');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_samesite', 'Strict');
session_start();
?>
Python/Flask:
from flask import Flask, session, make_response
app = Flask(__name__)
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Strict',
)
@app.route('/login')
def login():
response = make_response('Logged in')
response.set_cookie(
'authToken',
'token123',
secure=True,
httponly=True,
samesite='Strict',
max_age=3600
)
return response
Python/Django (settings.py):
# Session cookie settings
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Strict'
SESSION_COOKIE_AGE = 3600 # 1 hour
# CSRF cookie settings
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = 'Strict'
Ruby/Rails:
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
key: '_myapp_session',
secure: true,
httponly: true,
same_site: :strict,
expire_after: 1.hour
Java/Spring Boot:
// application.properties
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.same-site=strict
server.servlet.session.timeout=1h
// Or in Java config
@Configuration
public class CookieConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setUseHttpOnlyCookie(true);
serializer.setUseSecureCookie(true);
serializer.setSameSite("Strict");
return serializer;
}
}
ASP.NET Core:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSession(options =>
{
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.IdleTimeout = TimeSpan.FromHours(1);
});
// For authentication cookies
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Strict;
});
}
Fix 3: Web Server Configuration
Nginx (header modification):
# In nginx.conf or site config
location / {
# Modify Set-Cookie headers to add security flags
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=Strict";
# For existing cookies, use headers-more module
# more_set_headers 'Set-Cookie: $sent_http_set_cookie; Secure; HttpOnly; SameSite=Strict';
}
Apache (.htaccess):
# Modify Set-Cookie headers
Header edit Set-Cookie ^(.*)$ "$1; Secure; HttpOnly; SameSite=Strict"
# Or with mod_headers
<IfModule mod_headers.c>
Header always edit Set-Cookie (.*) "$1; Secure; HttpOnly; SameSite=Strict"
</IfModule>
Fix 4: Client-Side Cookie Setting (Limited)
JavaScript (NOT RECOMMENDED for session/auth cookies):
// β οΈ Can only set Secure and SameSite via JS
// Cannot set HttpOnly (defeats the purpose)
document.cookie = "preference=dark_mode; Secure; SameSite=Lax; Max-Age=31536000; Path=/";
// For non-sensitive cookies only
function setSecureCookie(name, value, days) {
const maxAge = days * 24 * 60 * 60;
const secure = window.location.protocol === 'https:' ? '; Secure' : '';
document.cookie = `${name}=${value}; SameSite=Lax${secure}; Max-Age=${maxAge}; Path=/`;
}
// β NEVER for session/auth cookies - use server-side instead
Fix 5: SameSite Attribute Values
Choose the right SameSite value for your use case:
Strict (Most Secure):
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict
- Never sent on cross-site requests
- Not even sent when clicking links from other sites
- Best for session/auth cookies
- May affect user experience (relogin after external links)
Lax (Balanced):
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax
- Sent on top-level GET navigation (clicking links)
- Not sent on cross-site POST, images, iframes
- Good default for most cookies
- Balances security and usability
None (Required for cross-site):
Set-Cookie: widget=abc123; Secure; SameSite=None
- Sent on all cross-site requests
- MUST include
Secureattribute - Required for embedded widgets, SSO, payment processors
- Only use when absolutely necessary
Comparison:
| Scenario | Strict | Lax | None |
|---|---|---|---|
| Link from external site | β Not sent | β Sent | β Sent |
| Form POST from external site | β Not sent | β Not sent | β Sent |
| Iframe on external site | β Not sent | β Not sent | β Sent |
| Image on external site | β Not sent | β Not sent | β Sent |
| AJAX from same site | β Sent | β Sent | β Sent |
Fix 6: WordPress-Specific
Add to wp-config.php:
<?php
// Secure session cookies
@ini_set('session.cookie_httponly', true);
@ini_set('session.cookie_secure', true);
@ini_set('session.use_only_cookies', true);
// For WordPress auth/session cookies, add to functions.php:
add_filter('send_auth_cookies', function($send) {
// WordPress handles this, but ensure HTTPS is enforced
if (!is_ssl() && !is_admin()) {
wp_redirect('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 301);
exit();
}
return $send;
});
Plugin approach:
// In theme functions.php or custom plugin
add_action('init', function() {
if (!is_ssl() && !is_admin()) {
wp_redirect('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 301);
exit();
}
});
// Cookie security plugin settings
// Recommended: "Really Simple SSL" plugin
// - Automatically adds Secure flag
// - Fixes mixed content
// - Easy configuration
Platform-Specific Guides
Detailed implementation instructions for your specific platform:
Verification
After implementing secure cookies:
Check DevTools Application/Storage tab:
- All session/auth cookies have
Secureβ - All session/auth cookies have
HttpOnlyβ - All cookies have
SameSiteset β - No warnings or yellow flags
- All session/auth cookies have
Test HTTP access (should fail):
# Try to access cookie over HTTP curl -I http://example.com # Cookies should NOT be returned # HTTPS should work curl -I https://example.com # Cookies should be returned with security flagsVerify JavaScript access blocked:
// In browser console console.log(document.cookie); // Should NOT show HttpOnly cookies (session, auth) // Only non-sensitive cookies should appearTest cross-site requests:
- Open DevTools β Network tab
- Visit external site linking to yours
- Click link to your site
- Check request headers
SameSite=Strictcookies should not be sentSameSite=Laxcookies should be sent on GET
Security scan verification:
- Run SecurityHeaders.com scan
- Run OWASP ZAP scan
- Check for cookie security warnings
- Should see improved security score
Common Mistakes
- Setting SameSite=None without Secure - Browsers reject this
- Using Secure flag over HTTP - Cookies won't be set
- Adding HttpOnly to functional cookies - Breaks JS that needs access
- Too strict SameSite - Users logged out when clicking external links
- Not updating subdomain cookies - Inconsistent security across subdomains
- Forgetting CSRF tokens - SameSite not enough for all CSRF protection
- Setting security flags client-side only - HttpOnly must be server-side
- No expiration time - Session cookies persist too long
- Testing only in development - Production HTTPS behaves differently
- Not documenting cookie purposes - Hard to determine correct security level
Cookie Security Checklist
Session/Authentication Cookies:
-
Secureflag enabled -
HttpOnlyflag enabled -
SameSite=StrictorSameSite=Lax - Reasonable expiration time (Max-Age)
- Path restricted to minimum needed
- Domain set appropriately
- HTTPS enforced site-wide
Preference/Tracking Cookies:
-
Secureflag enabled (if HTTPS) -
SameSite=Laxminimum - Longer expiration acceptable
- Not HttpOnly (if JS needs access)
-
Secureflag required -
SameSite=Noneonly if necessary - Review privacy implications
- Consider cookie consent requirements
General:
- All cookies over HTTPS
- Regular security audits
- Cookie policy documented
- Compliance with regulations (GDPR, CCPA)
Cookie Security by Type
Session Cookies (Highest Security)
Set-Cookie: sessionId=xyz789; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600
Authentication Tokens
Set-Cookie: authToken=abc123; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=86400
CSRF Tokens
Set-Cookie: csrfToken=def456; Secure; SameSite=Strict; Path=/; Max-Age=3600
Note: NOT HttpOnly - JavaScript needs to read this to include in requests
Remember Me Tokens
Set-Cookie: rememberToken=ghi789; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=2592000
User Preferences
Set-Cookie: theme=dark; Secure; SameSite=Lax; Path=/; Max-Age=31536000
Note: Can omit HttpOnly if JS needs to read
Analytics/Tracking
Set-Cookie: _ga=GA1.2.123456789; Secure; SameSite=Lax; Path=/; Max-Age=63072000