mass-assignment
Mass Assignment
When developers first started building web applications backed by databases, they often had to write raw SQL queries manually. This approach was repetitive, error-prone, and hard to maintain. To solve this, modern frameworks introduced Object-Relational Mapping (ORM) systems, which allow developers to interact with database records as objects instead of writing SQL directly.
Mass Assignment is a security vulnerability that occurs when an application automatically binds user input to object properties without proper filtering or restrictions. This can lead to unauthorized modification of sensitive fields, privilege escalation, and data breaches.
Common Technologies Affected
Mass Assignment is particularly prevalent in web frameworks that provide automatic data binding. Some common frameworks where this vulnerability can occur include:
- Ruby on Rails (Active Record)
- Node.js (Express + Mongoose)
- Laravel (PHP)
- Django (Python)
- Spring Boot (Java)
- ASP.NET Core (C# Model Binding)
Example of Mass Assignment Vulnerability
Vulnerable Code (Node.js + Mongoose)
- Node.js
- Django
- Laravel
// User schema in Mongoose
const UserSchema = new mongoose.Schema({
username: String,
email: String,
role: { type: String, default: "user" } // Shouldn't be modifiable by users
});
const User = mongoose.model("User", UserSchema);
// Insecure Route: Allows unrestricted updates
app.post("/update", async (req, res) => {
await User.updateOne({ _id: req.user.id }, req.body);
res.send("Updated successfully");
});
Issue: If an attacker submits { "role": "admin" }, they can escalate privileges.
from django.contrib.auth.models import User
from django.http import JsonResponse
def update_user(request):
if request.method == "POST":
user = User.objects.get(id=request.user.id)
data = request.POST # User-controlled input
for key, value in data.items():
setattr(user, key, value) # Mass assignment risk!
user.save()
return JsonResponse({"message": "Updated successfully"})
Issue: An attacker can send { "is_superuser": "true" } to escalate privileges.
use App\Models\User;
use Illuminate\Http\Request;
Route::post('/update', function (Request $request) {
$user = User::find(auth()->id());
$user->update($request->all()); // Mass assignment risk!
return response()->json(['message' => 'Updated successfully']);
});
Issue: If the request contains { "is_admin": 1 }, an attacker can gain admin privileges.
Secure Code (Whitelist Approach)
- Node.js
- Django
- Laravel
app.post("/update", async (req, res) => {
const safeFields = { username: req.body.username, email: req.body.email }; // Only allow these fields
await User.updateOne({ _id: req.user.id }, safeFields);
res.send("Updated successfully");
});
def update_user(request):
if request.method == "POST":
user = User.objects.get(id=request.user.id)
allowed_fields = ["first_name", "last_name", "email"] # Whitelist approach
for key, value in request.POST.items():
if key in allowed_fields:
setattr(user, key, value)
user.save()
return JsonResponse({"message": "Updated securely"})
Fillable Approach
Option 1: Use $fillable to allow only safe fields
class User extends Model
{
protected $fillable = ['name', 'email']; // Only allow safe fields
}
Option 2: Use only() to manually filter inputs
Route::post('/update', function (Request $request) {
$user = User::find(auth()->id());
$user->update($request->only('name', 'email')); // Explicitly allow only safe fields
return response()->json(['message' => 'Updated securely']);
});