Attacker-controlled subdomains set cookies with Domain=.example.com, planting a known session ID in the victim's browser before login on the main application.
TL;DR
Domain=.example.com cookies can set session cookies for the parent domain from any subdomain__Host- cookie prefix is the complete preventive control — browser rejects cross-subdomain cookie writes for __Host- prefixed namesDomain=.example.com (leading dot) in Set-Cookie is independently reportable as HIGH severityCross-subdomain session fixation exploits the browser's cookie scoping model to plant a known session identifier in the victim's browser from a subdomain, targeting the main application. The attack requires no XSS, no network interception, and no code execution on the main application — only the ability to issue HTTP responses from a subdomain in the same eTLD+1.
The key mechanism is the Domain attribute in Set-Cookie responses. When a server sets a cookie with Domain=.example.com (or Domain=example.com under RFC 6265), the browser accepts the cookie and includes it in requests to any subdomain of example.com, including the root. An attacker who gains control of static.example.com, uploads.example.com, or any other abandoned subdomain can issue a session cookie that the victim's browser will send to app.example.com or example.com on subsequent requests — including the login page.
The prerequisite — subdomain takeover — is one of the most consistently reported vulnerability classes in bug bounty programs. DNS records that remain active after the underlying service is decommissioned (GitHub Pages, Heroku, Netlify, Azure App Service, AWS S3 buckets with website hosting) are the primary source. Security teams that carefully monitor the main application while neglecting DNS lifecycle management create the attack surface.
The subdomain delivery response:
-- Attacker-controlled sub.example.com serves: --
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: session=ATTACKER_KNOWN_VALUE; Domain=.example.com; Path=/; Max-Age=86400
-- Note: no HttpOnly, no Secure needed on the subdomain response --
-- The browser will accept and store this for all .example.com domains --
<html><body><p>Loading...</p></body></html>The victim's browser on the next visit to the main application:
GET /login HTTP/1.1
Host: app.example.com
Cookie: session=ATTACKER_KNOWN_VALUE
-- Browser sends the subdomain-tossed cookie to the main app --After login (with non-rotation on main app):
POST /login HTTP/1.1
Host: app.example.com
Cookie: session=ATTACKER_KNOWN_VALUE
email=victim%40example.com&password=correct
HTTP/1.1 302 Found
Location: /dashboard
-- No Set-Cookie: session ID not rotated --| Technique | Subdomain Source | Cookie Attribute | Prevention |
|---|---|---|---|
| Subdomain takeover (CNAME) | Abandoned CNAME to cloud provider | Domain=.example.com | __Host- prefix |
| Expired subdomain | DNS record for decommissioned service | Domain=.example.com | __Host- prefix |
| Wildcard subdomain | *.example.com + HTTPS wildcard cert | Domain=.example.com | __Host- prefix |
| Developer/staging subdomain | dev.example.com with weak auth | Domain=.example.com | __Host- + subdomain hardening |
| XSS on subdomain | Stored XSS in sub.example.com | Can set Domain=.example.com | HttpOnly + __Host- |
Subdomain takeover is the most common prerequisite. Tools like subfinder, amass, and subzy can enumerate thousands of subdomains in minutes and identify those pointing to unclaimed cloud resources. The attack does not require compromising the cloud provider's infrastructure — merely claiming the specific resource the CNAME points to (an unclaimed Heroku app name, a GitHub Pages repo, a deleted Netlify site).
Wildcard subdomains (*.example.com) are particularly dangerous because the attacker only needs to register any subdomain under the wildcard. If the main application uses Domain=.example.com cookies, a newly registered subdomain can immediately toss session cookies.
HackerOne #1234231 — Auth0 Tenant Subdomain Cookie Tossing
An application using Auth0 with Domain=.app.example.com cookie configuration had an abandoned CNAME pointing to a decommissioned service. The attacker claimed the orphaned resource on the cloud provider, served a response with Set-Cookie: auth_session=KNOWN; Domain=.app.example.com, and then waited for users to visit the main Auth0 tenant. The non-rotated session after OAuth2 callback gave full account access. Medium-severity finding escalated to High due to admin account scope.
HackerOne #963569 — IBM WebSphere JSESSIONID URL Leak ($2,500)
JSESSIONID was embedded in URLs and leaked via Referer headers to third-party analytics scripts. Combined with Domain=.ibm.com session cookies, a compromised analytics provider could have tossed known session IDs for any *.ibm.com property. The finding motivated a session cookie scoping audit.
Subdomain Takeover + Session Fixation — Bug Bounty Pattern Multiple public HackerOne reports (various programs, 2023-2025) combine subdomain takeover with session fixation. The pattern: (1) find abandoned CNAME via subfinder/subzy, (2) claim the resource, (3) serve Set-Cookie with Domain=.target.com, (4) demonstrate that the planted cookie is sent to the main application. Bounties range from $500 (informational if main app has HttpOnly) to $5,000+ when full ATO is demonstrated.
CVE-2024-46977 — Gitea OAuth2 + Subdomain Context (CVSS 8.1)
While Gitea's primary issue was non-rotation after OAuth2, instances deployed with Domain=.org-domain.com cookie configuration were additionally exposed to subdomain tossing. Organizations using Gitea with wildcard subdomains for team repositories could have session cookies fixed from any team repository subdomain.
CVE-2024-21661 — Vaultwarden Cookie Domain Scope (CVSS 7.5)
Vaultwarden (the unofficial Bitwarden-compatible server) before 1.30.3 set session cookies with Domain=. prefix when configured under a subdomain deployment (e.g., vault.example.com). This caused the session cookie to be scoped to the parent domain rather than the specific host, enabling any subdomain of the deployment domain to perform cookie tossing. A self-hosted instance at vault.company.com would issue session cookies readable by any other *.company.com service, including developer subdomains with weaker security postures. Fixed in 1.30.3 by removing the leading-dot Domain attribute and adopting host-only cookie scoping.
npm package misconfigured cookie domain — Common pattern (2023–2025)
Multiple npm ecosystem packages for session management (including early versions of iron-session and some next-auth adapter configurations) defaulted to Domain=. cookie scoping when the domain option was set to the application hostname rather than left unset. This manifested in deployments where the library documentation showed domain: process.env.DOMAIN as an example, causing users to set Domain=.example.com without understanding the subdomain-scoping implications. Any subdomain under the same eTLD+1 could toss session cookies for the main application.
Domain=.example.com (leading dot) in a session cookie Set-Cookie header is independently reportable as a HIGH severity finding even when the application correctly rotates sessions on login. The leading dot enables cookie tossing from any subdomain and represents a defense-in-depth failure that widens the attack surface for future subdomain compromises.
Set-Cookie response header for the session cookie. Check for Domain=. (leading dot) or Domain=example.com (no subdomain prefix, RFC 6265 interpretation). Either configuration allows subdomain tossing.subfinder -d target.com -o subs.txt. Run subzy run --targets subs.txt to identify takeover candidates.Set-Cookie: test_cookie=FIXED; Domain=.target.com from a subdomain. Visit the main application and inspect cookies — if test_cookie=FIXED is present, tossing is confirmed.# Full subdomain takeover scan
subfinder -d target.com | tee subdomains.txt | httpx -silent | nuclei -t http/takeovers/
# Check for Domain= in session Set-Cookie
curl -sI https://target.com/login | grep -i "set-cookie" | grep -i "domain="
# subzy for subdomain takeover fingerprinting
subzy run --targets subdomains.txt --hide-fails --output vulnerable.txt
# nuclei subdomain takeover templates
nuclei -l subdomains.txt -t http/takeovers/ -o takeovers.txtBreachVex tests for cross-subdomain fixation by: auditing session cookie Domain attributes for leading-dot values or root-domain scope, running subdomain takeover detection against all discovered subdomains using cloud-provider fingerprints, and testing whether a planted cookie with the targeted Domain value is accepted by the main application's session handler.
__Host- Cookie PrefixThe single most effective control:
# Python FastAPI — __Host- prefix completely prevents subdomain tossing
response.set_cookie(
key="__Host-session", # __Host- prefix = host-only binding
value=new_session_token,
httponly=True,
secure=True, # required for __Host-
samesite="strict",
max_age=3600,
path="/", # required for __Host-
# domain= MUST NOT be set for __Host- to be valid
)// Java — __Host- prefix via ResponseCookie (Spring)
import org.springframework.http.ResponseCookie;
ResponseCookie sessionCookie = ResponseCookie.from("__Host-session", token)
.httpOnly(true)
.secure(true)
.sameSite("Strict")
.path("/")
.maxAge(Duration.ofHours(1))
// .domain() intentionally omitted — __Host- requires no domain
.build();
response.addHeader(HttpHeaders.SET_COOKIE, sessionCookie.toString());If __Host- prefix cannot be applied immediately (e.g., application served from non-root path):
# Remove Domain attribute — cookie becomes host-only
response.set_cookie(
key="session",
value=token,
httponly=True,
secure=True,
samesite="strict",
# domain= OMITTED — cookie scoped to exact hostname only
); PHP — remove domain scoping
; BAD: session.cookie_domain = ".example.com"
; GOOD: leave session.cookie_domain empty (host-only default)
session.cookie_domain = ""
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Strict"# Audit CNAME records pointing to cloud providers
# Check if each target is still claimed
dig +short CNAME www.example.com
dig +short CNAME static.example.com
dig +short CNAME assets.example.com
# Automate: compare CNAME targets against provider unclaimed fingerprints
subfinder -d example.com | dnsx -cname | grep -E "(github\.io|heroku\.com|netlify\.app|azurewebsites\.net|s3\.amazonaws\.com)"Implement CNAME record TTL monitoring. Alert on any subdomain CNAME pointing to an unclaimed cloud resource. Decommission DNS records simultaneously with the cloud service — never leave an active CNAME pointing to a deleted service.
An abandoned subdomain CNAME costs attackers approximately 5 minutes to exploit for session fixation delivery. The subdomain takeover itself (claiming a GitHub Pages repo or Heroku app) is often free and takes under 10 minutes. Organizations with more than 50 subdomains should implement automated subdomain monitoring (periodic subzy/nuclei scans) as standard practice, not just during security reviews.
Cross-subdomain session fixation uses a compromised or attacker-controlled subdomain to set cookies for the parent domain by including a Domain=.example.com attribute. Any subdomain of example.com can set cookies for the root domain if that subdomain can issue HTTP responses with Set-Cookie headers. An attacker who controls sub.example.com can plant a known session ID in the victim's browser, which is then sent to example.com on the next login — enabling session fixation without any XSS or MITM.
Subdomain takeover occurs when a DNS record (typically CNAME) points to a cloud service (GitHub Pages, Heroku, Netlify, Azure, AWS S3) that has been deprovisioned. An attacker who claims the resource at the target cloud service can serve HTTP responses from the subdomain, including Set-Cookie headers with Domain= attributes targeting the parent domain. Subdomain takeover is consistently one of the most-reported vulnerability classes in bug bounty programs.
The leading dot in Domain=.example.com (cookie v1 / Netscape spec) or the bare Domain=example.com (RFC 6265) both cause the browser to include the cookie in requests to all subdomains and the parent domain. A cookie set by sub.example.com with Domain=.example.com is sent to www.example.com, api.example.com, and example.com. Without the Domain attribute (or with __Host- prefix), the cookie is host-only and cannot be inherited by other subdomains or the parent.
Yes, completely. The __Host- prefix forces the browser to only accept cookies from the exact hostname that issued the response, with no Domain attribute, Secure flag required, and Path=/. A Set-Cookie header with __Host-session from sub.example.com is rejected for example.com. Any application that uses __Host- for its session cookie is immune to cross-subdomain fixation, regardless of how many subdomains are compromised.
Cookie tossing is a broader term for the technique where a subdomain sets cookies for a parent domain. Cross-subdomain session fixation is the application of cookie tossing specifically to session management — where the tossed cookie is a session identifier. The terms are often used interchangeably. Cookie tossing also encompasses non-fixation scenarios (e.g., overwriting CSRF tokens or authentication cookies with known values).
Any subdomain of the same eTLD+1 as the target application is valuable. High-value targets: static content subdomains (static.example.com, assets.example.com, cdn.example.com) because they are frequently provisioned on external CDNs and then forgotten. 'Old' subdomains (legacy.example.com, v1.example.com, dev.example.com) are common takeover candidates. Any CNAME pointing to a cloud provider where the original resource was deleted.
subzy checks DNS CNAME records against a fingerprint database of cloud providers that allow user-claimed resources (e.g., CNAME foo.azurewebsites.net — if foo.azurewebsites.net returns a 404 or specific 'unclaimed' page, the subdomain is vulnerable to takeover). It then verifies claimability by checking the response body against provider-specific unclaimed fingerprints. Subjack and nuclei's subdomain-takeover templates perform similar checks.
Cookie scoping rules: a subdomain can only SET cookies for its own domain or ancestors (Domain=.example.com). It cannot specifically overwrite a cookie that is host-bound (no Domain attribute). However, if the main application's session cookie uses Domain=.example.com, a compromised subdomain CAN overwrite it by issuing a new Set-Cookie with the same name and Domain=.example.com. The __Host- prefix prevents overwriting because its name-prefix constraints mean the subdomain's Set-Cookie for __Host-session would be rejected.
A CNAME chain attack occurs when multiple CNAME records exist between the subdomain and the final cloud endpoint. Security scanners sometimes miss takeovers that involve two or three CNAME hops (sub.example.com → alias.service.io → resource.cloudapp.com) because they only check the first CNAME. Tools like nuclei subdomain-takeover templates and subjack resolve the full CNAME chain to find deeply nested takeover candidates.
HSTS (includeSubDomains) prevents MITM delivery on subdomains but does not prevent cookie tossing from a legitimately owned or compromised subdomain. A subdomain that serves HTTPS responses (which it can, even after takeover, using Let's Encrypt) can still issue Set-Cookie headers with Domain=.example.com. HSTS does not restrict the scope of cookies a subdomain can set — only the __Host- prefix achieves that.