T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- references/api-development/SKILL.md:69
- Finding
- Unauthenticated User Registration Allows Administrative Role Assignment<![CDATA[ ## Vulnerability Details **File Location**: `references/api-development/SKILL.md`, lines 69–74 and 117–126 **Vulnerability Type**: Unauthenticated privilege assignment / mass assignment **Risk Level**: High ### Vulnerable Code ```typescript // POST /users router.post('/users', validateRequest(createUserSchema), async (req: Request, res: Response, next: NextFunction) => { try { const user = await userService.create(req.body) res.status(201).json(user) } catch (err) { next(err) } } ) ``` ```typescript export const createUserSchema = z.object({ body: z.object({ email: z.string().email('Invalid email format'), name: z.string().min(1, 'Name is required').max(100), password: z.string() .min(8, 'Password must be at least 8 characters') .regex(/[A-Z]/, 'Password must contain uppercase') .regex(/[0-9]/, 'Password must contain number'), role: z.enum(['user', 'admin']).default('user'), }), }) ``` ### Technical Analysis The example exposes `POST /users` without authentication or authorization middleware. Its validation schema permits the caller to submit either `user` or `admin` as the account role. The complete validated request body is then passed directly to `userService.create()`. A default value of `user` does not prevent exploitation because it is only applied when the caller omits the field. An attacker can explicitly provide `"role": "admin"`, which passes schema validation. This is an insecure mass-assignment pattern and violates the rule that authorization-sensitive properties must be assigned exclusively by trusted server-side logic. Because this Skill is intended to provide reusable production templates, applications adopting the example without correction may inherit the vulnerability. ### Attack Path 1. An unauthenticated attacker identifies the public `POST /users` endpoint. 2. The attacker submits a request such as: ```json { "email": "attacker@example.com", "na ...[truncated 843 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove `role` and every other authorization-sensitive field from the public registration schema. 2. Assign the least-privileged role in trusted server-side code, regardless of submitted input: ```typescript const publicRegistrationSchema = z.object({ body: z.object({ email: z.string().email(), name: z.string().min(1).max(100), password: z.string() .min(8) .regex(/[A-Z]/) .regex(/[0-9]/), }).strict(), }) const user = await userService.create({ ...req.body, role: 'user', }) ``` 3. Use `.strict()` or an equivalent allowlist mechanism to reject unknown properties instead of silently accepting or forwarding them. 4. Create a separate administrative endpoint for role assignment. 5. Protect that endpoint with both authentication and explicit authorization, such as `authenticate` followed by `authorize('admin')`. 6. Enforce authorization again in the service or domain layer so route-level middleware is not the only protection. 7. Add negative tests confirming that public registration cannot set `role`, permissions, ownership identifiers, account status, or similar privileged fields. ]]>
