Sensitive Cookie with Improper SameSite Attribute
Description
Sensitive Cookie with Improper SameSite Attribute occurs when the SameSite attribute for sensitive cookies is not set, or an insecure value is used. The SameSite attribute controls cross-domain cookie transmission with three possible values: Lax, Strict, or None. Using "None" without additional protections like anti-CSRF tokens enables potential Cross-Site Request Forgery (CSRF) attacks. When SameSite is not properly configured, browsers may send cookies with cross-site requests, potentially allowing attackers to perform unauthorized actions on behalf of authenticated users.
Risk
Improper SameSite configuration has significant security implications. CSRF attacks become possible. Session cookies may be sent cross-site. Unauthorized actions may be performed. User accounts may be compromised. Financial transactions may be manipulated. Data may be modified without consent. Privacy may be violated. Account takeover may occur.
Solution
Set the SameSite attribute of sensitive cookies to 'Lax' or 'Strict'. Lax permits cookies for top-level navigation via safe HTTP methods (GET, HEAD, OPTIONS, TRACE) but restricts state-mutating requests. Strict provides maximum protection but may affect user experience. Additionally, implement anti-CSRF tokens for state-changing operations regardless of SameSite setting.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Bypass Protection Mechanism - CSRF protections may be bypassed. |
| Integrity | Scope: Integrity Modify Application Data - Unauthorized state changes may occur. |
| Confidentiality | Scope: Confidentiality Read Application Data - Sensitive operations may be triggered. |
Example Code
Vulnerable Code
// Vulnerable: Session cookie without SameSite attribute
const express = require('express');
const session = require('express-session');
const app = express();
// VULNERABLE: No SameSite attribute specified
app.use(session({
secret: 'session-secret',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true
// VULNERABLE: SameSite not set
// Defaults to browser behavior (may be 'None')
}
}));
// VULNERABLE: State-changing action without CSRF protection
app.post('/transfer', (req, res) => {
const { to, amount } = req.body;
// Cookie sent with cross-site request!
if (req.session.user) {
transferFunds(req.session.user, to, amount);
res.json({ success: true });
}
});
// Attacker's malicious page can trigger transfer:
// <form action="https://bank.com/transfer" method="POST">
// <input name="to" value="attacker">
// <input name="amount" value="10000">
// </form>
// <script>document.forms[0].submit();</script>
# Vulnerable: Flask session without SameSite
from flask import Flask, session, request
app = Flask(__name__)
app.secret_key = 'secret-key'
# VULNERABLE: No SameSite configuration
app.config['SESSION_COOKIE_SAMESITE'] = None # Explicitly disabled!
app.config['SESSION_COOKIE_SECURE'] = True
@app.route('/change-email', methods=['POST'])
def change_email():
# VULNERABLE: No CSRF token validation
if 'user_id' in session:
new_email = request.form.get('email')
# Session cookie sent with cross-site POST
update_user_email(session['user_id'], new_email)
return 'Email updated'
return 'Unauthorized', 401
<?php
// Vulnerable: PHP session cookie without SameSite
// VULNERABLE: Default session cookie settings
session_start();
// Or explicitly vulnerable configuration
ini_set('session.cookie_samesite', ''); // Empty = no SameSite
// VULNERABLE: State-changing action
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['user_id'])) {
// No CSRF protection
$new_password = $_POST['password'];
// Cookie sent with cross-site request!
change_password($_SESSION['user_id'], $new_password);
echo "Password changed";
}
// Attacker's page triggers password change
?>
// Vulnerable: Java Servlet cookie without SameSite
import javax.servlet.http.*;
public class VulnerableSessionServlet extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response) {
// VULNERABLE: Session cookie without SameSite
HttpSession session = request.getSession(true);
// Create cookie without SameSite attribute
Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
sessionCookie.setHttpOnly(true);
sessionCookie.setSecure(true);
// VULNERABLE: No SameSite set
response.addCookie(sessionCookie);
// State-changing action without CSRF protection
if (session.getAttribute("user") != null) {
String action = request.getParameter("action");
performAction(action); // Vulnerable to CSRF
}
}
}
Fixed Code
// Fixed: Session cookie with proper SameSite attribute
const express = require('express');
const session = require('express-session');
const csrf = require('csurf');
const app = express();
// FIXED: SameSite attribute set to Strict
app.use(session({
secret: 'session-secret',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict', // FIXED: Strict SameSite
maxAge: 3600000 // 1 hour
}
}));
// FIXED: Additional CSRF protection
const csrfProtection = csrf({ cookie: false });
app.use(csrfProtection);
// FIXED: Protected state-changing action
app.post('/transfer', csrfProtection, (req, res) => {
const { to, amount } = req.body;
// FIXED: Cookie not sent cross-site due to SameSite=Strict
// FIXED: CSRF token also verified
if (req.session.user) {
transferFunds(req.session.user, to, amount);
res.json({ success: true });
}
});
// For APIs that need some cross-site functionality
// FIXED: Use Lax with additional CSRF protection
app.use('/api', session({
secret: 'api-session-secret',
cookie: {
httpOnly: true,
secure: true,
sameSite: 'lax', // FIXED: Lax for API compatibility
}
}));
// FIXED: Always verify CSRF for state-changing operations
app.post('/api/action', csrfProtection, (req, res) => {
// Protected by both SameSite=Lax and CSRF token
performAction(req.body);
});
# Fixed: Flask session with proper SameSite
from flask import Flask, session, request
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.secret_key = 'secret-key'
# FIXED: Enable CSRF protection
csrf = CSRFProtect(app)
# FIXED: Configure SameSite=Strict
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
@app.route('/change-email', methods=['POST'])
def change_email():
# FIXED: CSRF token automatically validated by Flask-WTF
if 'user_id' in session:
new_email = request.form.get('email')
update_user_email(session['user_id'], new_email)
return 'Email updated'
return 'Unauthorized', 401
# Alternative for APIs: Lax with explicit CSRF
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
@app.route('/api/action', methods=['POST'])
@csrf.exempt # Only if using different CSRF mechanism
def api_action():
# FIXED: Verify custom CSRF header for API
csrf_header = request.headers.get('X-CSRF-Token')
if not verify_csrf_token(csrf_header, session.get('csrf_token')):
return 'CSRF validation failed', 403
# Process action
return 'Success'
<?php
// Fixed: PHP session cookie with SameSite
// FIXED: Configure SameSite before session start
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.cookie_secure', '1');
ini_set('session.cookie_httponly', '1');
session_start();
// FIXED: Generate CSRF token
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// FIXED: Verify CSRF token for state-changing requests
function verify_csrf_token($token) {
return isset($_SESSION['csrf_token']) &&
hash_equals($_SESSION['csrf_token'], $token);
}
// FIXED: Protected state-changing action
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['user_id'])) {
// FIXED: Verify CSRF token
$csrf_token = $_POST['csrf_token'] ?? '';
if (!verify_csrf_token($csrf_token)) {
http_response_code(403);
die('CSRF validation failed');
}
$new_password = $_POST['password'];
change_password($_SESSION['user_id'], $new_password);
echo "Password changed";
}
// FIXED: Include CSRF token in forms
function csrf_field() {
return '<input type="hidden" name="csrf_token" value="' .
htmlspecialchars($_SESSION['csrf_token']) . '">';
}
?>
// Fixed: Java Servlet cookie with SameSite
import javax.servlet.http.*;
public class SecureSessionServlet extends HttpServlet {
protected void doPost(HttpServletRequest request,
HttpServletResponse response) {
HttpSession session = request.getSession(true);
// FIXED: Set session cookie with SameSite=Strict
String sessionId = session.getId();
String cookieValue = String.format(
"JSESSIONID=%s; HttpOnly; Secure; SameSite=Strict; Path=/",
sessionId
);
response.setHeader("Set-Cookie", cookieValue);
// FIXED: Verify CSRF token
String csrfToken = request.getHeader("X-CSRF-Token");
String sessionCsrf = (String) session.getAttribute("csrfToken");
if (csrfToken == null || !csrfToken.equals(sessionCsrf)) {
response.setStatus(403);
return;
}
// State-changing action now protected
if (session.getAttribute("user") != null) {
String action = request.getParameter("action");
performAction(action);
}
}
// FIXED: Generate CSRF token for session
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
HttpSession session = request.getSession(true);
if (session.getAttribute("csrfToken") == null) {
String token = generateSecureToken();
session.setAttribute("csrfToken", token);
}
// Include token in response for client use
response.setHeader("X-CSRF-Token",
(String) session.getAttribute("csrfToken"));
}
}
CVE Examples
SameSite cookie vulnerabilities have been found in various web applications where missing or improperly configured SameSite attributes enabled CSRF attacks against authenticated users.
Related CWEs
- CWE-923: Improper Restriction of Communication Channel to Intended Endpoints (parent)
- CWE-352: Cross-Site Request Forgery (can precede)
- CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute (related)
- CAPEC-62: Cross Site Request Forgery (attack pattern)
References
- MITRE Corporation. "CWE-1275: Sensitive Cookie with Improper SameSite Attribute." https://cwe.mitre.org/data/definitions/1275.html
- OWASP. "SameSite Cookie Attribute"
- RFC 6265bis. "Cookies: HTTP State Management Mechanism"