Cookie Security Testing: SameSite, HttpOnly, Secure Flags Deep Dive
Cookies are 30 years old and still the dominant mechanism for maintaining state on the web. Every session token, authentication cookie, and preference flag is a potential attack surface. The spec has grown — HttpOnly, Secure, SameSite, cookie prefixes — but adoption lags badly. Most production applications still issue session cookies with half the security attributes missing.
This guide covers how to test every meaningful cookie attribute, what missing flags actually enable an attacker to do, and how to structure findings in a pentest report or DAST output. The focus is practical: what to look for, how to verify it, and what the real-world impact is when you find a gap.
Why Cookies Still Matter in 2026
Tokens delivered via Authorization: Bearer headers have grown in popularity, particularly for SPA and API architectures. But session cookies remain the default for most server-rendered applications, OAuth callback flows, and legacy systems — and even SPAs often rely on cookies for refresh token storage.
The attack surface is significant. Session cookies authenticate every request. If an attacker can read one, they own the session. If they can force the browser to send one to an attacker-controlled endpoint, they can perform actions as the authenticated user. If the cookie persists indefinitely, a single theft gives long-term access.
Cookie security testing is also an area where automated scanners frequently produce shallow results — they check for the presence of a flag but not the correctness of its value or the interaction effects between flags. That gap is where real vulnerabilities live.
The Six Cookie Attributes That Define Security
Every cookie set by a server can carry a combination of these attributes. Understanding what each one does — and what happens when it is absent — is the foundation of cookie security testing.
- HttpOnly — Prevents JavaScript from accessing the cookie via
document.cookie. Blocks XSS-based session theft where the attacker can execute script but cannot exfiltrate the cookie directly. - Secure — Instructs the browser to transmit the cookie only over HTTPS connections. Without it, the cookie is sent over plain HTTP, making it readable by any network-level observer on the same path.
- SameSite — Controls whether the browser attaches the cookie to cross-site requests. Takes values
Strict,Lax, orNone. The primary CSRF mitigation mechanism in modern browsers. - Domain — Scopes the cookie to a specific domain and, optionally, its subdomains. Setting
Domain=example.commeans all subdomains receive the cookie — a significant expansion of scope that is often unintentional. - Path — Restricts the cookie to requests matching a URL path prefix. Rarely provides meaningful security isolation because the same-origin page at
/admincan read cookies set at/via JavaScript if HttpOnly is absent. - Expires / Max-Age — Controls cookie lifetime. Without these, the cookie is a session cookie and expires when the browser session ends (though "session" is browser-defined and not always what developers expect). With them, the cookie persists until the specified time.
Risk: Cookie misconfiguration is a production-grade vulnerability, not a theoretical one. Session hijacking via missing HttpOnly on XSS-vulnerable pages, CSRF via missing SameSite, and credential interception via missing Secure are all actively exploited in the wild. A single missing flag on a session cookie can be a critical finding depending on the application's threat model. In regulated environments (PCI DSS, HIPAA), missing Secure on authentication cookies is a compliance violation with direct audit impact.
Testing HttpOnly: What It Prevents and How to Verify It
HttpOnly is the most consistently tested cookie attribute, and also the most consistently misconfigured on legacy systems. The test is straightforward, but the interpretation requires context.
Response header check
Intercept the response that sets the session cookie — typically the login response or the first authenticated response after OAuth callback. In Burp Suite, look at the Set-Cookie header:
Set-Cookie: sessionid=abc123xyz; Path=/; HttpOnly; Secure; SameSite=Lax
If HttpOnly is absent:
Set-Cookie: sessionid=abc123xyz; Path=/
That is a finding. Severity depends on whether the application has XSS vulnerabilities — but you report it regardless, because the defense-in-depth argument holds even when no XSS is found during the engagement.
JavaScript access test
Open the browser developer console on any page in the application's origin and run:
document.cookie
If the session cookie appears in the output, HttpOnly is not set. If the session cookie does not appear — even though you are authenticated — HttpOnly is correctly configured. This is the definitive verification method because it tests actual browser behavior rather than just parsing a header string.
Note: some applications set multiple cookies. A tracking cookie visible via JavaScript does not indicate that the session cookie is accessible. Check specifically for the session identifier — typically named sessionid, PHPSESSID, JSESSIONID, .AspNetCore.Session, or whatever the framework default is.
Impact framing for reports
Missing HttpOnly on a session cookie allows an attacker who achieves XSS execution to exfiltrate the session token and perform account takeover without any user interaction beyond the initial XSS trigger. If HttpOnly is present, the attacker is limited to actions they can perform within the XSS payload execution context — still serious, but account takeover via stolen session token is blocked.
Testing the Secure Flag: When It's Missing and Why That Matters
The Secure flag tells the browser to never send the cookie over an unencrypted HTTP connection. Its absence means the cookie travels in plaintext whenever the browser makes an HTTP request to the same domain — which happens more often than most developers realize.
Downgrade attack scenario
Even if the application redirects all HTTP traffic to HTTPS, an attacker on the same network can intercept the initial HTTP request before the redirect response arrives. The browser sends the cookie with the first request. If Secure is not set, that first HTTP request carries the session token in plaintext.
This is an HSTS bypass vector when HSTS is not preloaded. The sequence is:
- User navigates to
http://example.com(typed URL or bookmark) - Browser sends
GET / HTTP/1.1withCookie: sessionid=abc123 - Attacker on the network path captures the cookie before the redirect
- Server responds with
301 Moved Permanentlyto HTTPS — too late
Mixed content scenarios
Applications that load any HTTP subresource from an HTTPS page trigger mixed content warnings, but more critically, if any HTTP request goes to the same domain, the browser will include non-Secure cookies. This includes analytics endpoints, error reporting beacons, and CDN assets served over HTTP that are on the same domain.
Verification
Check the Set-Cookie response header. Then confirm by attempting to access the application over HTTP directly and monitoring whether the cookie is sent in Burp's HTTP history. A session cookie without Secure appearing in an HTTP request is a confirmed finding.
# Check with curl — note the cookie in the request headers
curl -v -c cookies.txt https://target.com/login \
-d "username=test&password=test" 2>&1 | grep -i "set-cookie"
# Attempt HTTP access and observe whether the cookie is transmitted
curl -v -b cookies.txt http://target.com/ 2>&1 | grep -i "cookie"
SameSite: Strict, Lax, and None — What Each Actually Does
SameSite is the most nuanced cookie attribute and the one with the most implementation variance across browsers. It controls whether the browser attaches a cookie to requests that originate from a different site — which is the mechanism that CSRF attacks exploit.
SameSite=Strict — The cookie is never sent on any cross-site request, including navigations. If a user clicks a link from an external site to your application, the session cookie is not included in that first request. The user will appear unauthenticated until they make a same-site request (e.g., after a redirect). This is the most restrictive setting and effectively eliminates CSRF, but it also breaks legitimate cross-site entry points like links from emails or external dashboards.
SameSite=Lax — The cookie is sent on cross-site top-level navigations using safe HTTP methods (GET, HEAD, OPTIONS) but not on subresource requests (images, iframes, fetch/XHR) or unsafe methods (POST, PUT, DELETE). This is the browser default in Chrome since 2020 and Firefox since 2021 for cookies that do not explicitly set SameSite. It blocks most CSRF attacks while maintaining usability — a link from an email to your application will include the session cookie on the GET request.
SameSite=None — The cookie is sent on all cross-site requests including subresource loads, iframes, and POST requests from external forms. This value requires the Secure flag — browsers reject SameSite=None without Secure. It is intended for legitimate cross-site use cases like OAuth flows, payment widgets embedded in iframes, and third-party integrations. It provides no CSRF protection.
SameSite comparison table
| SameSite Value | Cross-Site GET Navigation | Cross-Site POST | Subresource / XHR | Third-Party iframe | CSRF Protection |
|---|---|---|---|---|---|
| Strict | Blocked | Blocked | Blocked | Blocked | Full |
| Lax | Allowed | Blocked | Blocked | Blocked | Partial (POST protected) |
| None (+ Secure) | Allowed | Allowed | Allowed | Allowed | None |
| Not set (browser default) | Lax behavior | Blocked (2-min grace in Chrome) | Blocked | Blocked | Partial (browser-dependent) |
The "browser default" row deserves attention. Chrome applies Lax-equivalent behavior to cookies without an explicit SameSite attribute, but with a 2-minute grace period where newly set cookies will be included in top-level cross-site POST requests. This exists for compatibility with OAuth redirect flows. It means that even with the browser default providing some protection, there is a narrow window where CSRF against POST endpoints is possible immediately after login.
Firefox applies similar defaults but with different edge cases. Safari historically treated SameSite=None without Secure by ignoring the SameSite attribute entirely rather than rejecting the cookie. Testing across browsers is not optional if SameSite behavior is relevant to your findings.
Testing SameSite=None Without Secure: The Fatal Combination
SameSite=None; Secure is the correct pattern for cookies that must work in cross-site contexts. SameSite=None without Secure is a broken configuration — modern browsers reject the cookie entirely (Chrome, Edge, Firefox) or silently ignore the SameSite=None directive (older Safari).
The testing scenario that matters is when developers set SameSite=None for a legitimate reason (OAuth, embedded widget) but forget the Secure flag. The result depends on browser version:
- Chrome 80+: Cookie is rejected. The application breaks in production for any cross-site use case it was designed to support.
- Older Safari: The SameSite=None is ignored, the cookie behaves as if SameSite is not set (roughly Lax). Cross-site POST requests may or may not include the cookie.
- Very old Chrome (pre-80): SameSite=None without Secure was accepted, making the cookie fully available cross-site over HTTP — a critical exposure.
To test this, intercept the Set-Cookie response and verify that any cookie with SameSite=None also carries the Secure flag. A cookie set as:
Set-Cookie: auth_token=xyz; SameSite=None; Path=/
is a finding that combines broken cross-site functionality with a potential security regression on older clients.
Domain Scope Attacks: Subdomain Cookie Theft
The Domain attribute controls which hosts receive a cookie. When set, it includes all subdomains. When omitted, the cookie is scoped to the exact host that set it.
The vulnerability pattern: application sets a session cookie with an explicit Domain=example.com attribute. This means api.example.com, staging.example.com, legacy.example.com — every subdomain — will receive that cookie on every request. If any subdomain is vulnerable to XSS or is outright compromised, the attacker gains access to the session cookie for the main application.
# Overly broad domain scope
Set-Cookie: session=abc123; Domain=example.com; Path=/; HttpOnly; Secure
# Correctly scoped — only app.example.com receives this cookie
Set-Cookie: session=abc123; Path=/; HttpOnly; Secure
# (no Domain attribute = host-only scope)
The counterintuitive rule: omitting the Domain attribute is more restrictive than setting it. Setting Domain=example.com explicitly opts into subdomain sharing. Omitting it scopes to the exact origin host.
Testing approach
Enumerate all subdomains of the target during reconnaissance. For each subdomain that receives the parent domain's session cookie, assess whether that subdomain represents an expanded attack surface — outdated software, user-generated content, unvalidated redirects. An XSS on a staging subdomain that shares cookies with the production application is a critical finding.
In Burp Suite, use the site map to observe which hosts receive cookies that were set by the main application. Any cross-subdomain cookie transmission that was not explicitly intended is worth investigating.
Cookie Prefixes: __Secure- and __Host-
Cookie prefixes are a browser-enforced mechanism that constrains how a cookie can be set. They are underused, rarely tested, and represent one of the clearest examples of a security control that provides meaningful protection at zero functional cost for most applications.
__Secure- prefix — The browser will only accept and store this cookie if it was set over HTTPS and includes the Secure flag. A server trying to set __Secure-session=abc over HTTP, or without the Secure attribute, will have the cookie silently rejected.
__Host- prefix — Stricter than __Secure-. The cookie must be set over HTTPS with the Secure flag, must not include a Domain attribute, and must have a Path of /. This locks the cookie to the exact host, prevents subdomain cookie sharing, and requires HTTPS transmission. It is the most secure cookie configuration available for session tokens.
# Correct __Host- prefix usage — browser enforces all constraints
Set-Cookie: __Host-session=abc123; Secure; Path=/; HttpOnly; SameSite=Lax
# __Secure- prefix — less strict, allows Domain attribute
Set-Cookie: __Secure-csrf_token=xyz; Secure; Path=/; SameSite=Strict
Testing for cookie prefix usage is a positive check — you are looking for whether applications use these protections, not just whether they are broken. In a security assessment, noting that session cookies do not use __Host- prefix is a low-to-medium finding depending on context. It is also an easy hardening recommendation that developers can implement in most frameworks with a one-line change to the cookie name.
Verify prefix enforcement by attempting to set a prefixed cookie in a way that violates the prefix rules — via HTTP, without the Secure flag, or with an explicit Domain attribute — and confirming the browser rejects it. In Burp Suite, modify the Set-Cookie response to add a prefix to an incorrectly configured cookie and observe whether the browser stores it.
Session Cookie Expiry: Testing Long-Lived Sessions
Long-lived session cookies are a force multiplier for session theft. If a session cookie persists for 30 days, a stolen token gives an attacker 30 days of access without needing to re-authenticate. The time window between theft and invalidation is the attacker's operational window.
What to look for
In the Set-Cookie header, check for Expires or Max-Age:
# 30-day persistent cookie — flag if this is a session token
Set-Cookie: session=abc123; Expires=Sun, 27 Jul 2026 00:00:00 GMT; Path=/; HttpOnly; Secure
# Session cookie — expires when the browser session ends
Set-Cookie: session=abc123; Path=/; HttpOnly; Secure
# (no Expires or Max-Age)
Session cookies (no Expires/Max-Age) expire when the browser session ends, which is typically when all browser windows are closed. However, browser behavior varies — Chrome and Firefox implement session restore, which preserves session cookies across browser restarts. Do not assume session cookies are short-lived in practice.
Accepted session durations
There is no universal correct answer, but reasonable defaults by application type are:
- Banking, healthcare, high-value transactions: 15–30 minutes idle timeout, absolute timeout of 8 hours
- Standard SaaS: 24-hour idle timeout, 7–30 day absolute timeout with re-authentication for sensitive actions
- Remember-me cookies: up to 30 days, but must be a separate token from the session — and must be rotated on use
Test whether the server actually enforces session expiry by capturing a session token, waiting past the idle timeout period, and replaying the token. Many applications set short cookie lifetimes but accept old tokens indefinitely because server-side session validation is missing or broken.
Session invalidation on logout
Capture the session cookie before logout. Perform logout. Attempt to use the captured cookie to make an authenticated request. If the server accepts it, the session was not invalidated server-side — the client deleted the cookie but the token is still valid. This is a critical finding independent of cookie attribute configuration.
Automated Cookie Security Testing Tools
Manual header inspection works for targeted testing, but at scale — assessing an application with dozens of endpoints that set cookies, or running continuous security validation — automated tooling is essential.
Burp Suite
Burp Scanner flags missing HttpOnly and Secure attributes as informational-to-low findings. More useful is the manual workflow: use the Proxy to capture all Set-Cookie headers, then use the Search function to identify cookies without specific flags. Burp's session handling rules can automate re-authentication for deeper scanning across authenticated endpoints.
For SameSite testing, use Burp Repeater to craft cross-origin requests and observe whether cookies are included. The Collaborator can help confirm out-of-band cookie exfiltration in XSS scenarios.
OWASP ZAP
ZAP's passive scanner includes rules for missing cookie security flags (rules 10010, 10011, 10012). These fire automatically during spidering and active scanning. ZAP also includes an anti-CSRF testing module that validates whether state-changing requests are protected by tokens or SameSite — useful for correlating SameSite findings with actual CSRF exploitability.
Custom scripts
For CI/CD integration, a lightweight Python script that parses Set-Cookie headers and flags missing attributes is often more actionable than a full scanner run:
import requests
def audit_cookies(url):
resp = requests.get(url, allow_redirects=True)
for header_name, header_value in resp.headers.items():
if header_name.lower() == 'set-cookie':
flags = header_value.lower()
issues = []
if 'httponly' not in flags:
issues.append('MISSING HttpOnly')
if 'secure' not in flags:
issues.append('MISSING Secure')
if 'samesite' not in flags:
issues.append('MISSING SameSite')
if issues:
print(f"Cookie: {header_value}")
print(f"Issues: {', '.join(issues)}\n")
audit_cookies('https://target.com/login')
What Ironimo catches
Ironimo's scanner checks every cookie set across authenticated and unauthenticated endpoints, including cookies set during OAuth flows, redirect chains, and error responses — surfaces that manual testers often miss. It flags missing HttpOnly, Secure, and SameSite attributes, detects SameSite=None without Secure, identifies overly broad Domain scope, and correlates missing cookie flags with related findings like XSS or CSRF to produce risk-weighted findings rather than isolated attribute checks. Cookie prefix usage (or the absence of it) is also reported as a hardening recommendation.
A Cookie Security Testing Checklist
Use this checklist for every web application assessment. Check all cookies that carry session tokens, authentication credentials, or CSRF tokens — not just cookies named "session".
- Identify all session-relevant cookies — Intercept login, OAuth callback, and post-authentication responses. List every
Set-Cookieheader and classify each cookie by function (session ID, CSRF token, preference, tracking). - Check HttpOnly on session cookies — Verify via response header and confirm with
document.cookiein browser console. - Check Secure flag on all cookies — Verify via response header. Confirm by making an HTTP (not HTTPS) request and observing whether cookies are transmitted.
- Check SameSite on session and CSRF cookies — Flag absence. Flag
SameSite=None. VerifySameSite=Nonealways has Secure. Correlate SameSite=None or missing SameSite with CSRF testing results. - Check Domain attribute scope — If Domain is set, enumerate subdomains and assess whether cookie sharing creates an expanded attack surface.
- Check cookie expiry — Verify Max-Age/Expires values on session tokens. Test server-side enforcement of expiry. Test session invalidation on logout.
- Test cookie prefix usage — Note whether
__Host-or__Secure-prefixes are used. If not, include as a hardening recommendation. - Test SameSite=None cross-site behavior — For any
SameSite=Nonecookie, verify it is required for a legitimate cross-site use case and is not a session token. - Correlate with XSS findings — For every XSS finding, check whether session cookies on the affected origin are HttpOnly. Escalate severity accordingly.
- Correlate with CSRF findings — For every state-changing endpoint, check whether session cookies on the affected origin have SameSite=Lax or Strict. Confirm whether CSRF tokens are also present as a secondary control.
- Check non-session cookies — Tracking and preference cookies without security flags are lower severity but worth noting, particularly in GDPR-regulated contexts where cookie consent and security intersect.
- Test cookie in error responses — Some frameworks issue new session cookies on error pages or redirects. Check 3xx, 4xx, and 5xx responses for Set-Cookie headers.
Run security scans the way pentesters do
Ironimo gives DevSecOps teams on-demand access to Kali Linux-powered scanning — DAST, API security testing, and automated vulnerability detection without the consulting invoice.
Start free scan