Skip to content

Don't adopt unknown session IDs from the request cookie - #2647

Open
BhavayGoyal wants to merge 2 commits into
drogonframework:masterfrom
BhavayGoyal:fix/session-unknown-cookie-id
Open

BhavayGoyal wants to merge 2 commits into
drogonframework:masterfrom
BhavayGoyal:fix/session-unknown-cookie-id

Conversation

@BhavayGoyal

Copy link
Copy Markdown

Fixes #2593

Problem

With sessions enabled, findSessionForRequest() hands any non-empty JSESSIONID cookie value to SessionManager::getSession(), and getSession() creates a new session under that key when it isn't in the cache. So the client chooses the session ID:

  • session fixation: an attacker can plant a known ID in a victim's browser and it becomes a real session,
  • every made-up ID adds an entry to the shared session cache (with the default timeout of 0 they never expire),
  • the IDs can be chosen to collide in the std::unordered_map behind CacheMap, which is the HashDoS described in the issue.

Fix

In SessionManager::getSession(), when the ID came from the cookie (needToSet == false):

  • if it names a live session, return that session (unchanged behaviour), otherwise
  • create the session under a new ID from the configured ID generator, with needToSet = true, so handleSessionForResponse() sends the new cookie.

So only server-generated IDs ever become cache keys. A client whose session expired (or who comes back after a server restart) just gets a new cookie, as it would without a cookie at all. Lookup of an existing session still refreshes its timeout as before (findAndFetch, like modify).

This covers the first suggestion in the issue. I didn't add a keyed hash or a session count cap here, to keep the change small; with only server-generated IDs as keys, clients can no longer pick colliding keys.

Testing

  • New SessionManagerTest unit test: an issued ID returns the same session; an unknown ID gets a new session with a fresh server-generated ID; that new ID then resolves; the unknown ID is never stored. Against the current code it fails 5 of 9 assertions (the attacker-chosen ID is adopted); with the fix all pass.
  • Full unittest suite passes (73 test cases, Ubuntu 24.04, GCC), and format.sh with clang-format-17 leaves no diff.

(Written with Claude, working with @BhavayGoyal.)

BhavayGoyal and others added 2 commits October 28, 2026 20:56
findSessionForRequest() passed any non-empty session cookie value to
SessionManager::getSession(), which created a session under that key if
none existed. A client could therefore pick its own session IDs: session
fixation, unbounded growth of the session cache, and keys chosen to
collide in the cache's unordered_map.

When the ID comes from the cookie and does not name a live session,
create the session under a new server-generated ID instead. It is marked
to be sent to the client, so the cookie is replaced on the response.
CI failures on the PR branch, both pre-existing in the test added
here:

- format: clang-format-17 reformats the lambda-taking constructor
  call in SessionManagerTest.cc to one line plus a hanging block,
  not the manually wrapped one it was written with.
- windows/msvc SHARED: the unit test links against drogon.dll and
  calls SessionManager's constructor and getSession() directly, but
  SessionManager (lib/src, internal) was never marked DROGON_EXPORT,
  so those symbols aren't in the DLL's export table on a Windows
  shared build -- LNK2019/LNK1120. MultipartStreamParser and
  HttpResponseImpl, the other lib/src classes with direct unit tests,
  already use this pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Potential HashDoS in the server-side session map from client-supplied session IDs

1 participant