Some vulnerabilities don’t need sophisticated exploits.
Sometimes all it takes is a backend that trusts user input a little too much.
Today I built a small Flask API to demonstrate a subtle but dangerous issue called Mass Assignment.
The Idea
Modern frameworks make development fast by automatically mapping user input into database objects. For example, when updating a user profile, developers often accept JSON data and apply it directly to the user record. Something like this:
user.update(request.json)
Convenient.
But also dangerous.
If the application blindly accepts every field sent by the client, an attacker can modify hidden or sensitive fields that were never meant to be user-controlled. This is where Mass Assignment vulnerabilities appear.
Here's a simple diagram illustrating how a mass assignment attack flows—from the client's malicious POST request straight to unintended database updates:
The Lab I Built
To demonstrate the issue, I created a small Flask API with:
- User login
- Profile viewing
- Profile update endpoint
- Admin panel
The database stores user information including an internal field:
is_admin
Normal users should never be able to modify this. But in the vulnerable version of the API, the update endpoint trusts the client completely.
I used SQLite for simplicity, but the principles apply to any ORM or direct query setup. The app runs on localhost:5000, and I tested it with tools like Postman and curl to simulate requests.
The Vulnerable Endpoint
The problem is here:
fields = ["username", "email", "password", "is_admin"]
for field in fields:
if field in data:
cur.execute(f"UPDATE users SET {field}=? WHERE id=?", (data[field], session["user_id"]))
The server accepts whatever fields appear in the request body. So if a user sends:
{
"email": "new@email.com",
"is_admin": 1
}
The application happily updates the admin flag. No authentication bypass. No memory corruption. Just a backend trusting the client.
Exploiting the Vulnerability
First I logged in as a normal user.
POST /login
{
"username": "testuser",
"password": "password123"
}
The API returned a session cookie.
Next, I attempted to update my profile but included a hidden parameter.
POST /update_profile
{
"email": "new@email.com",
"is_admin": 1
}
The server processed it without validation. Here's a visual walkthrough of a similar exploitation—showing the normal payload vs. the sneaky one that escalates privileges:
Finally, I accessed the admin panel.
GET /admin
And the system responded:
Welcome Admin. System secrets unlocked.
Privilege escalation achieved. In my tests, this took under 30 seconds from login to admin access.
Why This Happens
Mass Assignment vulnerabilities occur when:
- Frameworks automatically bind user input to objects
- Sensitive fields exist in the model
- Developers fail to restrict which fields can be updated
It is extremely common in:
- REST APIs
- ORMs (like SQLAlchemy in Python or ActiveRecord in Rails)
- Auto-binding frameworks (e.g., Spring in Java)
- JSON request handling
Real-world impacts? Think unauthorized role changes, over-claiming discounts in e-commerce, or even injecting payment details. OWASP lists this under A03:2021 – Injection (broader category), but it's sneaky because it feels like "just an update."
Fixing the Vulnerability
The correct solution is field whitelisting. Instead of accepting every parameter, the server must explicitly define which fields users are allowed to modify.
Example secure approach:
allowed_fields = ["username", "email", "password"]
for field in allowed_fields:
if field in data:
cur.execute(
f"UPDATE users SET {field}=? WHERE id=?",
(data[field], session["user_id"])
)
Now the system ignores any attempt to modify is_admin. Even if an attacker sends it in the request, the server will discard it.
For ORMs, use annotations like @JsonIgnore in Jackson or attr_readonly in SQLAlchemy to enforce this at the model level.
Key Lesson
This vulnerability isn't about complex hacking techniques. It's about trust boundaries. User input should never dictate how internal system fields behave. The backend must always decide what can and cannot be modified.
Results
Closing Thoughts
This small project reminded me of an important principle: Security flaws often hide in convenience. A feature designed to make development easier can quietly introduce risk if input validation is overlooked.
Today’s experiment showed how a single unvalidated parameter can transform a regular user into an administrator. And sometimes, the most dangerous bugs are the ones that look perfectly normal.
If you're building APIs, run a quick audit: Scan your update endpoints for whitelisting. Tools like OWASP ZAP or even a simple Burp Suite proxy can spot these fast.






Top comments (0)