Home/Journal/Pattern
Patternauthorizationmass-assignmentprivilege-escalation

When New Accounts Are Born as Admin

Self-registration that trusts a client role field or defaults new accounts to admin hands attackers privilege on signup. Here is the pattern and the fix.

Rahul Dharan··8 min read

What this is

A signup privilege-default vulnerability is a self-registration flow that hands new accounts more power than they should ever have. It takes three forms, and we see all three: a flow that trusts a role field the client sent and copies it into the account’s permissions; a flow that defaults every new account to a high-privilege role; and a flow that lets anyone self-provision into a privileged tenant. In each case the attacker does not escalate privilege after getting in. They register, and they are already in.

This is one of the highest-severity findings we report, because it collapses the whole authorization model in a single request. There is no chain to build. The account creation endpoint - the one surface a team most wants open to the public - is the exploit.

Why it keeps happening

Registration sits at an awkward seam. It is the one authenticated action a system must let unauthenticated strangers perform, and it writes the record that every later permission check will trust. Get the defaults wrong there and every downstream check faithfully enforces the wrong answer.

The most common cause is that the bug is in configuration, not code. A platform ships as several apps built from a shared codebase. One app is meant to allow public signup; another is not. The signup toggle gets left in the wrong state on the wrong app, and a registration flow that was safe by design on one deployment is wide open on the next. Nothing in the code looks wrong, because the code is the same code that is correct elsewhere.

Mass assignment has a different root: convenience. A framework that binds request fields straight to a model saves the developer from writing out each field by hand. It also, silently, lets the request name fields the developer never intended to expose - including the role. The signup form only shows email and password, so the role field is invisible in the UI and easy to forget exists in the payload. An attacker who reads the request does not forget.

And the high-privilege default is usually a leftover from early development, when the first accounts needed to be admins so the team could build. The default never got lowered before launch, and the account that seeds itself as admin is now anyone who signs up.

How the attack works

The mechanism is short by nature:

  1. Find the signup endpoint. Every product with accounts has one, and it is reachable by definition.
  2. Inspect the registration request. The attacker reads the real payload the form sends, not the fields the form displays. Where mass assignment is the flaw, they add a role field - "role": "admin" - and submit it alongside the normal ones.
  3. Register, then check what you got. They create the account and look at the privileges attached to it. Where the client role was trusted, the account is now admin. Where the default was high, they did not even need to send a role. Where the tenant was unscoped, they are inside someone else’s tenant with the data that comes with it.

No exploitation follows. The account they were handed is the finding.

What it looks like in the wild

We have found this across products with nothing in common but a public registration flow.

The consistent theme: the account-creation endpoint is meant to be public, so it gets the least suspicion, and a wrong default or a trusted field turns the most-open surface into the most-privileged one.

How to tell if you are exposed

A short self-check:

  1. Register a fresh account and read exactly what role and tenant it received. If a brand-new account holds any privilege beyond the minimum, your default is too high.
  2. Capture your own registration request and add fields the form does not show - role, is_admin, tenant_id, permissions. Submit them. If any change lands on the account, the server is trusting the client.
  3. Confirm which of your deployments have self-registration enabled, and whether every one of them is meant to. Look specifically for a shared codebase where signup is correctly off on one app and wrongly on for another.
  4. Register with an unverified email and see whether the account can act before verification. If it can, verification is cosmetic.

How to fix it

The takeaway

The account-creation endpoint is the one door you deliberately leave open to strangers, which is exactly why its defaults have to be the strictest in the system. If the client can name its own role, or a new account is born as admin, registration is not a signup flow - it is a privilege giveaway. See this pattern in the field in New Accounts Born as Executives and its siblings.

Frequently asked

What is a signup privilege-default vulnerability?
It is a self-registration flow that gives new accounts more privilege than they should have. Either it trusts a client-supplied role field and copies it into the account's permissions (mass assignment), it defaults new accounts to a high-privilege role, or it lets anyone self-provision into a privileged tenant. Any of the three means an attacker registers straight into power.
What is mass assignment in registration?
Mass assignment is when the server takes fields from the registration request and writes them into the account record without filtering. If the request includes a role field and the server copies it in, an attacker sets role to admin in the signup payload and the server grants it. The client should never be able to name its own role.
How do you secure a signup flow?
The server assigns the role, never the client. Default every new account to the least privilege that lets the product work. Reject unexpected fields in the registration payload, gate self-registration where it should not be open, and enforce email verification as a real control rather than a cosmetic step.

This is one finding from a harness that runs continuously. See how Greywatch finds, proves, and fixes them.

How it works →