Skip to content

fix: scope ClearNonCurrentSessions to current user only#21331

Open
harshinsecurity wants to merge 1 commit intosmartcontractkit:developfrom
harshinsecurity:fix/scope-clear-non-current-sessions-to-user
Open

fix: scope ClearNonCurrentSessions to current user only#21331
harshinsecurity wants to merge 1 commit intosmartcontractkit:developfrom
harshinsecurity:fix/scope-clear-non-current-sessions-to-user

Conversation

@harshinsecurity
Copy link

fix: scope ClearNonCurrentSessions to current user only

Problem

When any user changes their password (via PATCH /v2/user/password or the updateUserPassword GraphQL mutation), ClearNonCurrentSessions is called to invalidate the user's other sessions. However, the SQL query deletes every session in the table except the caller's current one — including sessions belonging to completely different users.

Affected code (all three authentication backends)

core/sessions/localauth/orm.go

DELETE FROM sessions WHERE id != $1

core/sessions/ldapauth/ldap.go

DELETE FROM ldap_sessions WHERE id != $1

core/sessions/oidcauth/oidc.go

DELETE FROM oidc_sessions WHERE id != $1

In all three cases the query filters only on session ID, with no WHERE clause constraining the deletion to the current user's email. The result is that when User A changes their password, User B, User C, etc. are all logged out.

Call sites

This function is invoked from two places:

  1. REST APIUserController.updateUserPassword in core/web/user_controller.go (line 348)
  2. GraphQLResolver.UpdateUserPassword in core/web/resolver/mutation.go (line 961)

Both pass only the current session ID, and neither has any additional user-scoping logic.

Impact

  • Any authenticated user (regardless of role) can log out every other user on the node by simply changing their own password.
  • The endpoint does not check that NewPassword ≠ OldPassword, so an attacker can repeatedly set the same password, wiping all other sessions each time.
  • With the default authenticated rate limit of 1000 requests/minute, this can be automated trivially.
  • This affects all three authentication backends (local, LDAP, OIDC).

What is NOT affected

  • API token authentication is independent of the sessions table and is unaffected. Users authenticating via API tokens (X-API-Key / X-API-Secret headers) will continue to work normally.
  • No data is corrupted or exposed — this is purely an availability issue for session-based (web UI) access.

Practical severity

Low-to-Medium. Most production Chainlink nodes are single-user, and programmatic access uses API tokens (unaffected). However, multi-user deployments do exist, and the behavior is clearly unintended — the function's own doc comment says "removes all sessions but the id passed in," which implies intent to clear only the caller's own sessions.

Fix

Adds an AND email = (SELECT email FROM <table> WHERE id = $1) subquery to all three implementations. This scopes the deletion to sessions owned by the same user as the current session, without changing the ClearNonCurrentSessions interface signature.

After fix

core/sessions/localauth/orm.go

DELETE FROM sessions WHERE id != $1 AND email = (SELECT email FROM sessions WHERE id = $1)

core/sessions/ldapauth/ldap.go

DELETE FROM ldap_sessions WHERE id != $1 AND user_email = (SELECT user_email FROM ldap_sessions WHERE id = $1)

core/sessions/oidcauth/oidc.go

DELETE FROM oidc_sessions WHERE id != $1 AND user_email = (SELECT user_email FROM oidc_sessions WHERE id = $1)

Why a subquery instead of adding an email parameter

The ClearNonCurrentSessions method is defined in the AuthenticationProvider interface (core/sessions/authentication.go), and is called from both the REST controller and the GraphQL resolver. Changing the function signature would require updating the interface, all three implementations, both call sites, and the mock. The subquery approach achieves the same result with zero interface changes — the session ID already contains enough information to resolve the owning user.

Files changed

File Change
core/sessions/localauth/orm.go Scoped DELETE to current user's email via subquery
core/sessions/ldapauth/ldap.go Scoped DELETE to current user's user_email via subquery
core/sessions/oidcauth/oidc.go Scoped DELETE to current user's user_email via subquery

How to verify

  1. Create two users (User A and User B) on a Chainlink node
  2. Log both users in via the web UI (creating sessions for each)
  3. Have User A change their password via PATCH /v2/user/password
  4. Before fix: User B's session is deleted — they are logged out
  5. After fix: User B's session is preserved — only User A's other sessions are cleared

Contact

Reported by: hi@harshinsecurity.in

ClearNonCurrentSessions previously deleted every session in the table
except the caller's current session ID, regardless of which user owned
them. This meant that when any user changed their password, all other
users were logged out.

Fix by adding a subquery that resolves the owning user's email from the
current session ID and constraining the DELETE to only that user's
sessions. This preserves the existing interface signature.

Affected backends: localauth (sessions), ldapauth (ldap_sessions),
oidcauth (oidc_sessions).

Reported-by: hi@harshinsecurity.in
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.

1 participant