{"id":"CVE-2026-58269","aliases":["EUVD-2026-84115","GHSA-92cr-jxw4-5wjg"],"status":"published","title":"Sync-in Server has a complete 2FA Bypass via `POST /api/auth/token`","description":"Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, `POST /api/auth/token` authenticates with username and password only, then calls `getTokens()`, which returns full access and refresh JWTs without checking whether the account has TOTP 2FA enabled. An attacker with stolen or phished credentials can bypass 2FA in a single request. The parallel login endpoint (`POST /api/auth/login`) correctly enforces 2FA by calling `setCookies(user, res, true)`, which gates on `user.twoFaEnabled`. Version 2.4.0 patches the issue.","severity":{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00274,"percentile":0.20024,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-288"],"published":"2026-09-21T20:01:56Z","modified":"2026-10-28T02:31:10Z","fixes":[{"ecosystem":"npm","package":"@sync-in/server","fixed_in":["2.4.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"@sync-in/server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"2.3.0"},{"fixed":"2.4.0"}]}]},{"source":"osv","ecosystem":"npm","package":"@sync-in/server","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2.4.0"}]}]}],"products":[{"vendor":"devolutions","product":"server"},{"vendor":"sync-in","product":"server"}],"references":[{"url":"https://github.com/Sync-in/server/security/advisories/GHSA-92cr-jxw4-5wjg","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-58269","tags":["advisory"]},{"url":"https://github.com/Sync-in/server/pull/228","tags":["web"]},{"url":"https://github.com/Sync-in/server/commit/3ec74e2ea1f538fe1a3ac9487bdf24a19e548361","tags":["web"]},{"url":"https://github.com/Sync-in/server/releases/tag/v2.4.0","tags":["web"]},{"url":"https://github.com/advisories/GHSA-92cr-jxw4-5wjg"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/58xxx/CVE-2026-58269.json","tags":["advisory"]},{"url":"https://github.com/Sync-in/server","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-58269","title":"Sync-in Server has a complete 2FA Bypass via `POST /api/auth/token`","description":"Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, `POST /api/auth/token` authenticates with username and password only, then calls `getTokens()`, which returns full access and refresh JWTs without checking whether the account has TOTP 2FA enabled. An attacker with stolen or phished credentials can bypass 2FA in a single request. The parallel login endpoint (`POST /api/auth/login`) correctly enforces 2FA by calling `setCookies(user, res, true)`, which gates on `user.twoFaEnabled`. Version 2.4.0 patches the issue.","status":"published","published":"2026-09-21T20:01:56.322Z","modified":"2026-09-24T22:38:27.013Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high","assessor":"cna"}],"cwes":["CWE-288"],"updated_at":"2026-11-02T13:47:28.691429Z"}],"euvd":[{"id":"EUVD-2026-84115","description":"Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, `POST /api/auth/token` authenticates with username and password only, then calls `getTokens()`, which returns full access and refresh JWTs without checking whether the account has TOTP 2FA enabled. An attacker with stolen or phished credentials can bypass 2FA in a single request. The parallel login endpoint (`POST /api/auth/login`) correctly enforces 2FA by calling `setCookies(user, res, true)`, which gates on `user.twoFaEnabled`. Version 2.4.0 patches the issue.","status":"published","published":"2026-09-21T20:01:56Z","modified":"2026-09-24T22:38:27Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high"}],"updated_at":"2026-11-02T17:27:55.846756Z"}],"ghsa":[{"id":"GHSA-92cr-jxw4-5wjg","title":"Sync-in Server has a complete 2FA Bypass via `POST /api/auth/token`","description":"**Affected component:** Sync-in Server v2.3.0, `POST /api/auth/token` (`auth.controller.ts:50-55`).\n\n**Required attacker capability:** Valid username and password for a 2FA-enabled account.\n\n## Summary\n\n`POST /api/auth/token` authenticates with username and password only, then calls `getTokens()`, which returns unrestricted Bearer access and refresh JWTs without checking whether the account has TOTP 2FA enabled. An attacker who already knows valid credentials for a 2FA-enabled account can bypass 2FA in a single request.\n\nThe parallel login endpoint (`POST /api/auth/login`) correctly enforces 2FA by calling `setCookies(user, res, true)`, which gates on `user.twoFaEnabled` when server-side TOTP is enabled.\n\n## Details\n\nThe token endpoint at `auth.controller.ts:50-55` uses `AuthLocalGuard` (password-only) and calls `getTokens()` directly:\n```typescript\n// auth.controller.ts:50-55\n@Post(AUTH_ROUTE.TOKEN)\n@AuthTokenSkip()\n@UseGuards(AuthLocalGuard)\ntoken(@GetUser() user: UserModel): Promise\u003cTokenResponseDto\u003e {\n  return this.authManager.getTokens(user)\n}\n```\n`getTokens()` at `auth.service.ts:25-39` signs and returns access and refresh JWTs. It never reads `user.twoFaEnabled`:\n```typescript\n// auth.service.ts:25-39\nasync getTokens(user: UserModel, refresh = false): Promise\u003cTokenResponseDto\u003e {\n  const currentTime = currentTimeStamp()\n  // ...expiration logic...\n  return {\n    [TOKEN_TYPE.ACCESS]: await this.jwtSign(user, TOKEN_TYPE.ACCESS, accessExpiration),\n    [TOKEN_TYPE.REFRESH]: await this.jwtSign(user, TOKEN_TYPE.REFRESH, refreshExpiration),\n    // ...\n  }\n}\n```\nCompare with the login endpoint at `auth.controller.ts:30-35`, which calls `setCookies(user, res, true)`. Inside `setCookies()` at `auth.service.ts:45`, the 2FA gate fires:\n```typescript\n// auth.service.ts:45\nconst verify2Fa = init2FaVerify \u0026\u0026 configuration.auth.mfa.totp.enabled \u0026\u0026 user.twoFaEnabled\n```\nWhen `verify2Fa` is true, `setCookies()` issues only a restricted `ACCESS_2FA` token and requires the user to complete `POST /api/auth/2fa/login/verify` before receiving full session cookies. The token endpoint has no equivalent gate.\n\n## PoC\n\n### Prerequisites\n\n- A Sync-in instance with TOTP 2FA enabled server-wide.\n- A user account with 2FA enrolled (the target).\n- The target's valid login and password, but not the TOTP secret or current TOTP code.\n\n### Steps\n\n1. **Enable 2FA on the target account.** Log in as the target user, navigate to Settings, and enable TOTP two-factor authentication.\n\n2. **Confirm normal login requires 2FA.** Log out. Log back in with the target's credentials. The UI presents a TOTP code prompt before granting access, and the API response contains only `token.access_2fa_expiration` (a restricted partial token):\n```\nPOST /api/auth/login\n{\"login\":\"test\",\"password\":\"...\"}\n\nResponse: {\"user\":{\"twoFaEnabled\":true},\"server\":{\"twoFaEnabled\":true},\"token\":{\"access_2fa_expiration\":1781234379}}\n```\n3. **Bypass 2FA via the token endpoint.** Send the same credentials to `/api/auth/token`:\n```\nPOST /api/auth/token\n{\"login\":\"test\",\"password\":\"...\"}\n\nResponse:\n{\n  \"access\": \"eyJhbGciOiJIUzI1NiIs...\",\n  \"refresh\": \"eyJhbGciOiJIUzI1NiIs...\",\n  \"access_expiration\": 1781235890,\n  \"refresh_expiration\": 1781248490\n}\n```\nUnrestricted Bearer access and refresh JWTs are returned. No TOTP code was required.\n\n4. **Confirm API access.** Use the token on a protected endpoint:\n```\nGET /api/users/me\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIs...\n\nResponse: {\"user\":{\"id\":16,\"login\":\"test\",\"email\":\"test@lab.local\",\"twoFaEnabled\":true,...}}\n```\nThe server returns the user profile. Protected API endpoints that accept Bearer authentication are accessible as the target user. No TOTP code was required at any step.\n\n### Measured observations\n\n- The login endpoint (`/api/auth/login`) correctly returns a restricted 2FA-pending response.\n- The token endpoint (`/api/auth/token`) returns unrestricted Bearer JWTs with the same credentials and no TOTP.\n- The returned Bearer token grants access to protected API endpoints as a fully authenticated user, though cookie-specific flows may differ.\n\n## Impact\n\nAn attacker who already knows valid credentials for a 2FA-enabled account can obtain unrestricted Bearer access and refresh JWTs in a single HTTP request, without knowing the TOTP secret or possessing the authenticator device. 2FA security is bypassed for Bearer-token API authentication.\n\n## Remediation\n\nGate the token endpoint behind the same 2FA policy used by the login route. After `AuthLocalGuard` validates the username and password, require a valid TOTP code when server-side TOTP is enabled and `user.twoFaEnabled` is true, before calling `getTokens()`.","status":"published","published":"2026-09-22T14:46:30Z","modified":"2026-09-22T14:46:32Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-288"],"updated_at":"2026-11-02T14:41:19.455935Z"}],"nvd":[{"id":"CVE-2026-58269","description":"Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, `POST /api/auth/token` authenticates with username and password only, then calls `getTokens()`, which returns full access and refresh JWTs without checking whether the account has TOTP 2FA enabled. An attacker with stolen or phished credentials can bypass 2FA in a single request. The parallel login endpoint (`POST /api/auth/login`) correctly enforces 2FA by calling `setCookies(user, res, true)`, which gates on `user.twoFaEnabled`. Version 2.4.0 patches the issue.","status":"published","published":"2026-09-21T20:17:26.837Z","modified":"2026-09-24T23:17:12.46Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high","assessor":"secondary"}],"cwes":["CWE-288"],"updated_at":"2026-11-02T14:12:50.368368Z"}],"osv":[{"id":"CVE-2026-58269","title":"Sync-in Server has a complete 2FA Bypass via `POST /api/auth/token`","description":"Sync-in Server is an open-source platform for file storage, sharing, collaboration, and syncing. Prior to version 2.4.0, `POST /api/auth/token` authenticates with username and password only, then calls `getTokens()`, which returns full access and refresh JWTs without checking whether the account has TOTP 2FA enabled. An attacker with stolen or phished credentials can bypass 2FA in a single request. The parallel login endpoint (`POST /api/auth/login`) correctly enforces 2FA by calling `setCookies(user, res, true)`, which gates on `user.twoFaEnabled`. Version 2.4.0 patches the issue.","status":"published","published":"2026-09-21T20:01:56.322Z","modified":"2026-10-28T02:31:10.00336498Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-288"],"updated_at":"2026-11-02T15:45:12.016252Z"},{"id":"GHSA-92cr-jxw4-5wjg","title":"Sync-in Server has a complete 2FA Bypass via `POST /api/auth/token`","description":"**Affected component:** Sync-in Server v2.3.0, `POST /api/auth/token` (`auth.controller.ts:50-55`).\n\n**Required attacker capability:** Valid username and password for a 2FA-enabled account.\n\n## Summary\n\n`POST /api/auth/token` authenticates with username and password only, then calls `getTokens()`, which returns unrestricted Bearer access and refresh JWTs without checking whether the account has TOTP 2FA enabled. An attacker who already knows valid credentials for a 2FA-enabled account can bypass 2FA in a single request.\n\nThe parallel login endpoint (`POST /api/auth/login`) correctly enforces 2FA by calling `setCookies(user, res, true)`, which gates on `user.twoFaEnabled` when server-side TOTP is enabled.\n\n## Details\n\nThe token endpoint at `auth.controller.ts:50-55` uses `AuthLocalGuard` (password-only) and calls `getTokens()` directly:\n```typescript\n// auth.controller.ts:50-55\n@Post(AUTH_ROUTE.TOKEN)\n@AuthTokenSkip()\n@UseGuards(AuthLocalGuard)\ntoken(@GetUser() user: UserModel): Promise\u003cTokenResponseDto\u003e {\n  return this.authManager.getTokens(user)\n}\n```\n`getTokens()` at `auth.service.ts:25-39` signs and returns access and refresh JWTs. It never reads `user.twoFaEnabled`:\n```typescript\n// auth.service.ts:25-39\nasync getTokens(user: UserModel, refresh = false): Promise\u003cTokenResponseDto\u003e {\n  const currentTime = currentTimeStamp()\n  // ...expiration logic...\n  return {\n    [TOKEN_TYPE.ACCESS]: await this.jwtSign(user, TOKEN_TYPE.ACCESS, accessExpiration),\n    [TOKEN_TYPE.REFRESH]: await this.jwtSign(user, TOKEN_TYPE.REFRESH, refreshExpiration),\n    // ...\n  }\n}\n```\nCompare with the login endpoint at `auth.controller.ts:30-35`, which calls `setCookies(user, res, true)`. Inside `setCookies()` at `auth.service.ts:45`, the 2FA gate fires:\n```typescript\n// auth.service.ts:45\nconst verify2Fa = init2FaVerify \u0026\u0026 configuration.auth.mfa.totp.enabled \u0026\u0026 user.twoFaEnabled\n```\nWhen `verify2Fa` is true, `setCookies()` issues only a restricted `ACCESS_2FA` token and requires the user to complete `POST /api/auth/2fa/login/verify` before receiving full session cookies. The token endpoint has no equivalent gate.\n\n## PoC\n\n### Prerequisites\n\n- A Sync-in instance with TOTP 2FA enabled server-wide.\n- A user account with 2FA enrolled (the target).\n- The target's valid login and password, but not the TOTP secret or current TOTP code.\n\n### Steps\n\n1. **Enable 2FA on the target account.** Log in as the target user, navigate to Settings, and enable TOTP two-factor authentication.\n\n2. **Confirm normal login requires 2FA.** Log out. Log back in with the target's credentials. The UI presents a TOTP code prompt before granting access, and the API response contains only `token.access_2fa_expiration` (a restricted partial token):\n```\nPOST /api/auth/login\n{\"login\":\"test\",\"password\":\"...\"}\n\nResponse: {\"user\":{\"twoFaEnabled\":true},\"server\":{\"twoFaEnabled\":true},\"token\":{\"access_2fa_expiration\":1781234379}}\n```\n3. **Bypass 2FA via the token endpoint.** Send the same credentials to `/api/auth/token`:\n```\nPOST /api/auth/token\n{\"login\":\"test\",\"password\":\"...\"}\n\nResponse:\n{\n  \"access\": \"eyJhbGciOiJIUzI1NiIs...\",\n  \"refresh\": \"eyJhbGciOiJIUzI1NiIs...\",\n  \"access_expiration\": 1781235890,\n  \"refresh_expiration\": 1781248490\n}\n```\nUnrestricted Bearer access and refresh JWTs are returned. No TOTP code was required.\n\n4. **Confirm API access.** Use the token on a protected endpoint:\n```\nGET /api/users/me\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIs...\n\nResponse: {\"user\":{\"id\":16,\"login\":\"test\",\"email\":\"test@lab.local\",\"twoFaEnabled\":true,...}}\n```\nThe server returns the user profile. Protected API endpoints that accept Bearer authentication are accessible as the target user. No TOTP code was required at any step.\n\n### Measured observations\n\n- The login endpoint (`/api/auth/login`) correctly returns a restricted 2FA-pending response.\n- The token endpoint (`/api/auth/token`) returns unrestricted Bearer JWTs with the same credentials and no TOTP.\n- The returned Bearer token grants access to protected API endpoints as a fully authenticated user, though cookie-specific flows may differ.\n\n## Impact\n\nAn attacker who already knows valid credentials for a 2FA-enabled account can obtain unrestricted Bearer access and refresh JWTs in a single HTTP request, without knowing the TOTP secret or possessing the authenticator device. 2FA security is bypassed for Bearer-token API authentication.\n\n## Remediation\n\nGate the token endpoint behind the same 2FA policy used by the login route. After `AuthLocalGuard` validates the username and password, require a valid TOTP code when server-side TOTP is enabled and `user.twoFaEnabled` is true, before calling `getTokens()`.","status":"published","published":"2026-09-22T14:46:30Z","modified":"2026-09-22T15:00:48.89956434Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-288"],"updated_at":"2026-11-02T15:46:43.123867Z"}]}}
