← Files DescopeARCHIVED FILE
references/vulnerability-catalog.md
15.6 KB · Oct 2, 2026 · 00:09 UTC
# Vulnerability Catalog
Walk every category. For each, run the detection heuristics, then **read** the matched file to confirm. Never report from a grep hit alone.
Severity defaults assume an internet-facing service with real users. Downgrade one level for internal-only or pre-auth-gated surfaces.
---
## 1. Broken or Missing Authentication
### 1.1 Endpoint exposed without authentication (CWE-306)
**Detect:** handlers in the inventory marked `auth required? = n` that mutate state, return user data, or touch admin functionality. Grep for handlers that never invoke `getSession`, `currentUser`, `req.user`, `@login_required`, `[Authorize]`, `authenticate!`, or a known guard.
**Fix:** add auth middleware globally and opt *out* for public endpoints explicitly (allow-list, not deny-list).
**Severity:** High.
### 1.2 Hand-rolled password compare (CWE-208, CWE-327)
**Detect:** `password ==`, `password ===`, `strcmp`, `Equals(password`, `.equals(password`. Compares not using `bcrypt.compare`, `argon2.verify`, `crypto.timingSafeEqual`, `hmac.compare_digest`, or `MessageDigest.isEqual`.
**Fix:** constant-time compare + modern password hash (argon2id preferred, bcrypt acceptable).
**Severity:** Medium (High when timing-observable over an API).
### 1.3 Plaintext or weak password hash (CWE-256, CWE-916)
**Detect:** `md5`/`sha1`/`sha256` over a password field; `password:` column written directly; no hash library imported in auth code.
**Fix:** argon2id or bcrypt (cost ≥ 12). Migrate on next login.
**Severity:** High.
### 1.4 Authentication bypass via SQL injection (CWE-89)
**Detect:** string concatenation or `f"...{input}..."` in `SELECT ... WHERE username = ...`; raw `exec`/`query`/`execute` with user input. Check the login handler specifically.
**Fix:** parameterized queries / ORM query builder.
**Severity:** High.
### 1.5 User enumeration via differential responses (CWE-203)
**Detect:** login/reset/signup returning different messages (`"user not found"` vs `"wrong password"`), different status codes, or different timing branches.
**Fix:** single generic message; uniform timing.
**Severity:** Low (Medium if no rate limit).
### 1.6 Credentials in code, config, or logs (CWE-798, CWE-532)
**Detect:** `password\s*=\s*["']`, `api[_-]?key\s*=\s*["']`, `secret\s*=\s*["']`, `Bearer [A-Za-z0-9._-]{20,}`, `-----BEGIN`, `AKIA[0-9A-Z]{16}`. Also `console.log`/`logger.info`/`print` with `password`/`token`/`authorization`.
**Fix:** env/secret manager; scrub logs.
**Severity:** High for live secrets; Medium for credentials in logs.
---
## 2. JWT / Token Validation
### 2.1 `alg: none` accepted (CWE-347)
**Detect:** `algorithms=None`, `algorithms=['none']`, `jwt.decode(...)` without an `algorithms=` arg, `verify: false`, `verify_signature: false`.
**Fix:** explicit algorithm allow-list containing only your algorithm.
**Severity:** High.
### 2.2 Algorithm confusion (RS256 → HS256) (CWE-347)
**Detect:** accepted algorithms list includes both HS* and RS*, or dynamic algorithm selection, with verify key passed as a symmetric secret.
**Fix:** restrict to a single algorithm; for RS256 verify with public key only.
**Severity:** High.
### 2.3 Unverified decode (CWE-345)
**Detect:** `jwt.decode(` without `verify=True` or key; `jsonwebtoken` `decode()` used where `verify()` is required; `jose` `decodeJwt` used for authz decisions.
**Fix:** use verifying API (`jwt.verify`, `jwtVerify`). `decode` is inspection-only.
**Severity:** High.
### 2.4 Missing claim validation (CWE-345)
**Detect:** verification that skips `iss`, `aud`, `exp`, `nbf`, or `sub`. Flags: `ignore_exp`, `verify_exp=False`, absent `audience:` parameter.
**Fix:** validate `iss`, `aud`, `exp`, `nbf` on every request. For OIDC also validate `nonce` and `azp`.
**Severity:** Medium (High if `exp` skipped).
### 2.5 Weak, hardcoded, or committed JWT secret (CWE-321, CWE-798)
**Detect:** secrets < 32 bytes, strings like `"secret"`/`"changeme"`, `.env.example` values matching production.
**Fix:** 32+ random bytes from a CSPRNG; secret manager; rotate on exposure.
**Severity:** High if production; Medium for dev-only.
### 2.6 Tokens accepted from query string (CWE-598)
**Detect:** `req.query.token`, `request.args.get('token')`, routes accepting `?access_token=`. URLs leak via logs, referrers, history.
**Fix:** require `Authorization: Bearer` header or HTTP-only cookie.
**Severity:** Medium.
---
## 3. Session Management
### 3.1 Missing cookie flags (CWE-614, CWE-1004, CWE-1275)
**Detect:** `res.cookie(`/`Set-Cookie`/session middleware config — `httpOnly: false`, absent `secure`, absent `sameSite`, or `SameSite=None` without `Secure`.
**Fix:** `HttpOnly; Secure; SameSite=Lax` (or Strict). Use `None; Secure` only for known cross-site needs.
**Severity:** Medium.
### 3.2 Session not invalidated on logout (CWE-613)
**Detect:** logout clears cookie client-side only; no server-side revocation list, no session record deletion, no `jti` denylist for JWTs.
**Fix:** server-side invalidation. For stateless JWTs: short TTL + revocation list keyed on `jti` or user+iat.
**Severity:** Medium.
### 3.3 Session fixation (CWE-384)
**Detect:** login does not regenerate session ID. Absence of `session.regenerate`, `request.session.cycle_key()`, `reset_session`.
**Fix:** regenerate session ID on every privilege change (login, MFA, role/password change).
**Severity:** Medium.
### 3.4 Predictable or low-entropy session/token IDs (CWE-330, CWE-340)
**Detect:** `Math.random`, `rand()`, `new Random()`, `uuid1` (time-based), `Date.now()`-seeded IDs in auth code.
**Fix:** `crypto.randomBytes`, `secrets.token_urlsafe`, `SecureRandom`, framework CSPRNG.
**Severity:** High.
### 3.5 Long-lived or never-expiring sessions (CWE-613)
**Detect:** `maxAge: Infinity`, multi-year cookie expiry, refresh tokens with no rotation, absent inactivity timeout.
**Fix:** short access tokens, rotating refresh tokens with replay detection, absolute session maximum.
**Severity:** Low (Medium for admin/financial).
---
## 4. Broken Access Control / IDOR / BOLA
### 4.1 Object ID from request, no ownership check (CWE-639 / OWASP API1)
**Detect:** handlers using `req.params.id`, `request.args['id']`, `@PathVariable Long id`, `params[:id]` to fetch a record without comparing owner to authenticated principal. Grep `findById`, `get_object_or_404`, `.findOne(`, `.FindByID(` and inspect.
**Fix:** scope queries by owner: `findOne({ id, userId: currentUser.id })`, or explicit `assert record.owner_id == user.id`. For multi-tenant, scope by tenant too.
**Severity:** High.
### 4.2 Predictable object IDs (CWE-340)
**Detect:** auto-incrementing integer IDs in URLs with per-user resources. Amplifies 4.1 — not a standalone vuln.
**Fix:** UUIDv4/v7 or opaque IDs, **in addition to** a real ownership check.
**Severity:** Low alone.
### 4.3 Missing function-level authorization / BFLA (CWE-862 / OWASP API5)
**Detect:** admin endpoints (`/admin/`, `/internal/`, handler names with `delete`/`promote`/`impersonate`/`grant`/`refund`) that require auth but no role check. Absence of `isAdmin`/`role ==`/`@PreAuthorize`/`has_role` on these handlers is the finding.
**Fix:** role/permission check at handler or via a guard scoped to the admin router.
**Severity:** High.
### 4.4 Authorization via client-supplied role (CWE-602, CWE-807)
**Detect:** `req.body.role`, `req.headers['x-user-role']`, `params[:admin]`, `user_id` from request body instead of session. `req.user = req.body.user` patterns.
**Fix:** derive principal and role from session/token only.
**Severity:** High.
### 4.5 Tenant / organization crossing (CWE-863)
**Detect:** multi-tenant app filtering queries by resource ID but not by `tenantId`/`orgId`/`workspace_id`. Missing filter = crossing risk.
**Fix:** enforce tenant scope in ORM/repository (row-level security, default scopes, or required parameter).
**Severity:** High.
### 4.6 Authorization trusting request object for identity (CWE-639)
**Detect:** `if req.body.userId === token.sub` or `if resource.owner === req.body.ownerClaim` — attacker controls the compared value.
**Fix:** compare against server-resolved values only.
**Severity:** High.
---
## 5. Privilege Escalation & Mass Assignment
### 5.1 Mass assignment on user object (CWE-915)
**Detect:** `User.create(req.body)`, `user.update(params)`, `Object.assign(user, req.body)`, `**request.json()`, Active Record `update_attributes(params[:user])` without strong params. Flag when `role`/`is_admin`/`permissions`/`tenant_id`/`email_verified`/`mfa_enabled` are fields on the model.
**Fix:** explicit allow-list of updatable fields; DTOs; `permit(:name, :email)`.
**Severity:** High.
### 5.2 Self-service role change (CWE-269)
**Detect:** profile-update endpoint accepting `role`/`groups`/`scope` in body. Admin-promote endpoints reachable without admin check.
**Fix:** split role mutation into a separate admin-only endpoint.
**Severity:** High.
### 5.3 Horizontal escalation via parameter tampering
**Detect:** endpoints taking `userId`/`accountId` from request and acting on the named account without verifying it matches session.
**Fix:** default to session user; require an explicit admin path to act on others.
**Severity:** High.
---
## 6. OAuth / OIDC / SAML
### 6.1 Missing `state` / PKCE (CWE-352)
**Detect:** authorization-code callback handlers without `state` verification. For public clients: absent `code_challenge`/`code_verifier`.
**Fix:** cryptographic `state` bound to session, verified on callback. Require PKCE (`S256`) for public clients.
**Severity:** High.
### 6.2 Open redirect on OAuth callback / `returnTo` (CWE-601)
**Detect:** callback handlers redirecting to `redirect_uri`/`returnTo`/`next`/`continue` from the request without allow-list validation.
**Fix:** allow-list of hosts; prefer relative paths.
**Severity:** High.
### 6.3 ID token / access token not validated (CWE-345)
**Detect:** OIDC ID tokens passed through without signature/`iss`/`aud`/`nonce`/`exp` checks. Access token treated as identity.
**Fix:** verify ID token per OIDC spec. Access tokens are not identity assertions.
**Severity:** High.
### 6.4 Implicit flow in use (CWE-522)
**Detect:** `response_type=token` or `response_type=id_token token`; tokens in URL fragment.
**Fix:** migrate to authorization code + PKCE.
**Severity:** Medium.
### 6.5 SAML signature not verified / XSW (CWE-347)
**Detect:** SAML handlers parsing assertions without validating signature, or validating outer `Response` only while trusting inner `Assertion` content (XML Signature Wrapping).
**Fix:** vetted SAML library with XSW mitigations; validate signatures on the exact trusted elements.
**Severity:** High.
### 6.6 SSO account linking without verification (CWE-287)
**Detect:** first-login flow linking IdP account to existing local account by email alone, without owner consent.
**Fix:** re-auth to existing account or email confirmation before linking.
**Severity:** High.
---
## 7. Password Reset & Account Recovery
### 7.1 Predictable or non-expiring reset tokens (CWE-330, CWE-613)
**Detect:** `Math.random`, sequential IDs, tokens without expiry, tokens that persist until used.
**Fix:** CSPRNG tokens, 32+ bytes, ≤15 min TTL, single-use (invalidate on use and on new request).
**Severity:** High.
### 7.2 Host-header poisoning in reset link (CWE-20, CWE-640)
**Detect:** reset emails built from `req.headers.host`/`request.get_host()`/`request.url` without a configured canonical origin.
**Fix:** build reset URLs from a server-configured base URL.
**Severity:** High.
### 7.3 User enumeration in reset / signup (CWE-203)
**Detect:** reset replying "email not found" vs "email sent".
**Fix:** identical response regardless of existence.
**Severity:** Low.
### 7.4 Recovery bypasses MFA (CWE-287)
**Detect:** password reset flow that logs user in or clears MFA without re-enrollment.
**Fix:** reset sets new password only; MFA remains required; notify user.
**Severity:** High.
### 7.5 Email / phone change without re-auth (CWE-287)
**Detect:** profile endpoint allowing email/phone change without current-password or MFA challenge; no confirmation to the **old** address.
**Fix:** require re-auth; confirm on both old and new before taking effect.
**Severity:** High.
---
## 8. MFA & Step-Up
### 8.1 MFA step skippable (CWE-287)
**Detect:** login issues full session before MFA completes; MFA step reachable by direct "after MFA" endpoint call; remember-device cookie without integrity binding.
**Fix:** pre-auth session flagged unverified until MFA completes; bind remember-device tokens to user, device, recent IP.
**Severity:** High.
### 8.2 Missing rate limit on OTP (CWE-307)
**Detect:** OTP verify handler with no attempt counter, no lockout, short OTP (< 6 digits).
**Fix:** per-OTP attempt cap, per-IP rate limit, OTP length ≥ 6, short TTL.
**Severity:** High.
### 8.3 Sensitive actions without step-up (CWE-862)
**Detect:** password change, email change, API-key creation, payment change without re-challenge.
**Fix:** step-up within a short window before sensitive mutations.
**Severity:** Medium.
---
## 9. Rate Limiting & Enumeration
### 9.1 No rate limit on auth endpoints (CWE-307)
**Detect:** login/signup/reset/OTP/refresh/SSO-callback without rate limiter. Grep middleware for `rateLimit`/`Throttle`/`limiter`/`express-rate-limit`/`ratelimit-spring`.
**Fix:** per-IP and per-account limits; exponential backoff; CAPTCHA after N failures.
**Severity:** Medium (High if also no account lockout on login).
### 9.2 Rate limit keyed only on IP (CWE-307)
**Detect:** limiter scoped by `req.ip` only — ineffective against credential stuffing from botnets.
**Fix:** also key by username/email; track failed attempts per account.
**Severity:** Medium.
---
## 10. CSRF & CORS
### 10.1 State-changing GET (CWE-352)
**Detect:** `GET` handlers that write DB, send emails, charge payments.
**Fix:** move to `POST`/`PUT`/`DELETE`; CSRF token or SameSite cookie policy on mutations.
**Severity:** Medium.
### 10.2 Missing CSRF on cookie-auth endpoints (CWE-352)
**Detect:** cookie session without CSRF middleware (`csurf`, `django-csrf`, `Rack::Csrf`) and without `SameSite=Lax/Strict`.
**Fix:** `SameSite=Lax` minimum plus CSRF token on sensitive mutations, or switch to `Authorization` header.
**Severity:** Medium.
### 10.3 Permissive CORS with credentials (CWE-942, CWE-346)
**Detect:** `Access-Control-Allow-Origin: *` with `Allow-Credentials: true`; reflective origin without allow-list; `null` origin accepted.
**Fix:** exact allow-list; never reflect unvalidated; never trust `null`.
**Severity:** High if credentialed; Medium otherwise.
---
## 11. Identity-adjacent SSRF & Token Leakage
### 11.1 SSRF reaching metadata / internal identity (CWE-918)
**Detect:** HTTP clients called with user-supplied URLs and no allow-list. In cloud envs this can reach `169.254.169.254` and steal instance credentials.
**Fix:** allow-list hosts; block RFC1918, loopback, link-local; resolve DNS and re-check.
**Severity:** High.
### 11.2 Tokens logged or returned in errors (CWE-532, CWE-209)
**Detect:** `console.error(err)`, `logger.error(req)`, `res.json({ error: err })` in auth paths. Error responses echoing request or stack.
**Fix:** structured errors; redact auth headers and tokens; generic client messages.
**Severity:** Medium.
---
## Quick heuristics (run these greps first, then read matches)
```
rg -n "^(export\s+)?(async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)" --type=ts
rg -n "req\.(body|query|params)\.(user_?id|role|is_?admin|tenant)"
rg -n "findById\(|get_object_or_404|findOne\(|FindByID\("
rg -n "jwt\.decode\(|jsonwebtoken.*decode\(|verify:\s*false|verify_signature\s*=\s*False"
rg -n "algorithms\s*=\s*\[?['\"]?none" -i
rg -n "Math\.random|rand\(\)|uuid1\("
rg -n "cookie.*(httpOnly|secure|sameSite)" -i
rg -n "Access-Control-Allow-Origin.*\*"
rg -n "md5|sha1" -i
rg -n "password\s*[=:]=\s*"
```
Confirm every match by reading the file.
SHA-256: 314b034d0812213bf1e8e6ae16783ec777aebd8cb2dd85cd72894b9dd8a8d48c