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

ImpactDetails
Access ControlScope: Access Control

Bypass Protection Mechanism - CSRF protections may be bypassed.
IntegrityScope: Integrity

Modify Application Data - Unauthorized state changes may occur.
ConfidentialityScope: 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.


  • 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

  1. MITRE Corporation. "CWE-1275: Sensitive Cookie with Improper SameSite Attribute." https://cwe.mitre.org/data/definitions/1275.html
  2. OWASP. "SameSite Cookie Attribute"
  3. RFC 6265bis. "Cookies: HTTP State Management Mechanism"