A missing cache-control instruction in one of Python's most widely deployed web frameworks turned a routine performance optimization into a session-hijacking vector. CVE-2023-30861 affects Flask's default session interface: when an application's response content changes based on whether a valid session cookie is present, and that response passes through a shared or intermediary cache that doesn't factor cookies into its cache key, one user's session cookie can end up served to a completely different user. The result is session disclosure — and potentially account takeover — not through a code-execution bug or an injection flaw, but through the quiet interaction of a web framework default and a caching layer that most teams never audit for this kind of risk.
What CVE-2023-30861 actually is
Flask ships a default session implementation, SecureCookieSessionInterface, that stores session data client-side in a signed cookie rather than server-side. That design choice is fine on its own, but it means the framework needs to tell any caching layer sitting in front of the application that responses can legitimately differ per-cookie. Prior to the fix, Flask did not automatically add a Vary: Cookie header to responses when session data was read or written.
Without that header, a caching proxy, CDN, or reverse proxy that caches by URL alone (ignoring cookies) has no signal that the response it just cached is specific to the user who requested it. If that response happened to include a Set-Cookie header carrying session state — for example, a login flow that sets a fresh session cookie and immediately renders a personalized page — the cache can store and replay that exact response, cookie and all, to the next visitor who requests the same URL. The practical effect is one user's session token landing in another user's browser, which in a worst case allows the second user to impersonate the first.
This is not a Flask-specific coding mistake in the traditional sense — it's a missing safety default for a scenario that only manifests when three conditions line up at once: the app uses cookie-based sessions, the app's output is genuinely cookie-dependent, and something between the app and the client caches responses without accounting for that dependency. That combination is common enough in production — think CDN-fronted Flask apps, API gateways with response caching enabled, or Varnish/nginx proxy caches placed for performance — that the maintainers treated it as a security-relevant defect rather than a documentation gap.
Affected versions and components
- Flask versions prior to 2.2.5 (2.2.x branch)
- Flask 2.3.0 and 2.3.1 (2.3.x branch, prior to the 2.3.2 patch)
- Any application using Flask's default
SecureCookieSessionInterfacefor session management - Deployments that sit behind a shared cache, CDN, or reverse proxy configured to cache responses without keying on cookies or without stripping
Set-Cookieheaders from cached entries
Applications that don't rely on Flask's built-in session cookie (for example, those using server-side session stores accessed via a separate identifier that isn't cache-sensitive in the same way) or that explicitly disable caching for all authenticated routes are not exposed by this specific defect, though the underlying "cache-keying versus per-user content" problem is worth auditing regardless of framework.
The fix landed in Flask 2.2.5 and Flask 2.3.2, both of which add the Vary: Cookie header automatically whenever the session is accessed, giving downstream caches the signal they need to avoid mixing up responses across users.
CVSS, EPSS, and KEV context
NVD scored CVE-2023-30861 as CVSS v3.1 base score 7.5 (High), reflecting a network-exploitable, low-complexity condition with no privileges or user interaction required and a high confidentiality impact (session data disclosure) but no direct integrity or availability impact from the flaw itself. The severity rating is driven primarily by how damaging session disclosure can be downstream — a leaked session cookie is frequently equivalent to a leaked authentication token.
That said, the EPSS score for this CVE has remained low since disclosure, reflecting the fact that this isn't a "throw one request at it" remote exploit — it requires a specific deployment topology (cookie-dependent responses plus a misconfigured or overly permissive cache) to actually manifest. It has not been added to CISA's Known Exploited Vulnerabilities (KEV) catalog, and there are no widely reported instances of active exploitation in the wild. In practical terms, this means the vulnerability is real and worth remediating promptly, but it's a configuration-dependent disclosure risk rather than an actively weaponized, mass-exploited bug — the kind of finding that should be prioritized based on your own architecture (do you cache Flask responses behind a shared proxy or CDN?) rather than treated as an emergency drop-everything patch for every Flask deployment universally.
Timeline
- Early May 2023 — Flask maintainers identify and validate the session cookie caching issue, tracked under GitHub Security Advisory GHSA-m2qf-hxjv-5gpq.
- May 1, 2023 — Flask 2.2.5 and 2.3.2 are released, adding the
Vary: Cookieheader to responses whenever session data is accessed, closing the caching gap. - Shortly after release — CVE-2023-30861 is reserved and the advisory is published publicly, detailing the root cause and remediation guidance for affected deployments.
- Following weeks — the advisory is picked up by dependency-scanning tools, distro security trackers, and SCA vendors, prompting a wave of Flask version bumps across the ecosystem as automated scanners begin flagging pre-2.2.5 and pre-2.3.2 installs.
Because the underlying weakness required cache misconfiguration to be exploitable rather than being a straightforward remote code path, disclosure did not trigger the kind of mass emergency patching seen with, say, a critical deserialization bug — but it did surface a broader class of "framework defaults meet caching infrastructure" issues that security teams have continued to reference when reviewing CDN and reverse-proxy configurations.
Remediation steps
- Upgrade Flask immediately. Move to Flask 2.3.2 or later (or 2.2.5 if you must stay on the 2.2.x line). This is the authoritative fix — it makes Flask emit
Vary: Cookieautomatically whenever session state is touched, which is sufficient for most standard cache implementations to key correctly. - Audit every caching layer in front of Flask. Check CDN configuration (Cloudflare, Fastly, Akamai, CloudFront), reverse proxy rules (nginx
proxy_cache, Varnish VCL), and any API gateway response caching for whether cookies are included in the cache key and whetherSet-Cookieheaders are stripped from cached responses. Don't assume the framework fix alone covers a misconfigured cache that ignoresVaryheaders entirely. - Explicitly mark session-bearing responses as non-cacheable where feasible, using
Cache-Control: private, no-storeon any route that sets or reads session cookies and returns user-specific content. Defense in depth here matters — don't rely solely onVary: Cookieif your cache doesn't respect it correctly. - If you cannot upgrade immediately, apply a compensating control: manually set the
Vary: Cookieheader on affected views, or disable caching for those routes at the proxy/CDN layer until the framework upgrade can be scheduled. - Rotate session secret keys and force re-authentication if you have reason to believe cached responses containing session cookies were served across users during the exposure window — treat it the same as any suspected session token leak.
- Add Flask (and its transitive dependencies) to continuous SCA/dependency scanning so that future advisories affecting your web framework are caught automatically rather than relying on manual tracking of release notes.
- Extend the review beyond Flask. The same "cache doesn't know a response varies by cookie" pattern can affect other frameworks and custom middleware. Use this CVE as a prompt to review cache-key configuration for any authenticated or session-aware endpoint across your stack, not just Flask apps.
How Safeguard Helps
Safeguard is built to catch exactly this kind of framework-and-configuration interaction before it reaches production and to prioritize it correctly once it's found. Our SBOM generation and ingestion pipeline identifies every Flask instance and version across your services automatically, so vulnerable 2.2.x and pre-2.3.2 deployments surface without teams having to manually audit dependency manifests. Reachability analysis goes further than version matching alone — it determines whether your application actually uses Flask's default session interface and whether session-dependent routes sit behind a caching layer, distinguishing genuinely exploitable exposure from theoretical CVE noise that would otherwise inflate your backlog. Griffin AI correlates that reachability signal with the low EPSS score and absence from CISA's KEV catalog to help teams triage this finding against real risk rather than CVSS alone, while auto-fix PRs can open a pull request bumping Flask to a patched version and flagging affected cache configuration for review, cutting remediation time from days to minutes. Together, these capabilities turn a subtle, deployment-dependent disclosure bug into a finding your team can find, understand, and close with confidence.