logo

Database

Insecurely generated cookies - SameSite

Need

Restriction of session cookies to same-site requests

Context

• Usage of Rust 1.75+ for building application services

• Usage of the actix-web crate for handling HTTP requests and cookies

Description

1. Non compliant code

use actix_web::cookie::{Cookie, SameSite};

fn session_cookie(session_id: String) -> Cookie<'static> {
    // SameSite=None sends the session with requests started by any website
    Cookie::build("session_id", session_id)
        .path("/")
        .http_only(true)
        .secure(true)...

The `session_cookie` function below builds the session cookie with `SameSite::None`. `SameSite=None` tells the browser to attach the cookie to requests that other websites trigger: form submissions, image tags, fetch calls and navigations started from any domain. A malicious page visited by an authenticated user can therefore submit a form to the application, and the request arrives with a valid session. This is cross-site request forgery (CSRF), and it lets the attacker change the email, transfer funds or perform any action the user is allowed to do. `SameSite=None` is only needed when the application is deliberately embedded in or called by other sites, which is rarely the case for a session cookie.

2. Steps

• Set `.same_site(SameSite::Strict)` on session and authentication cookies, or `SameSite::Lax` when external links must reach authenticated pages.

• Remove `SameSite::None` from cookies that carry credentials, including `cookie_same_site(SameSite::None)` in `actix_session::SessionMiddleware`.

• When a cross-site integration really requires `SameSite::None`, use a separate cookie for it and always combine it with `.secure(true)`.

• Validate an anti-CSRF token on every state-changing request in addition to the `SameSite` attribute.

• Reject state-changing requests that use `GET`, so `SameSite::Lax` cannot be bypassed through top-level navigations.

3. Secure code example

use actix_web::cookie::{Cookie, SameSite};

fn session_cookie(session_id: String) -> Cookie<'static> {
    // Strict: the browser only sends the session on same-site requests
    Cookie::build("session_id", session_id)
        .path("/")
        .http_only(true)
        .secure(true)...

The corrected function sets `SameSite::Strict`. The browser now attaches the cookie only to requests that start on the application itself, so a form, script or link on another site cannot send an authenticated request. `SameSite::Lax` is an alternative when users arrive from external links and must already be logged in on the first page: it allows the cookie on top-level `GET` navigations but still blocks it on cross-site `POST`, `PUT` and `DELETE` requests and on embedded resources. `SameSite` is a strong defense in depth, but state-changing endpoints should still validate an anti-CSRF token, because older browsers and same-site subdomains are not covered by the attribute.