Skip to Content
Course content

172: Preventing Mass Assignment Vulnerabilities

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Wait, how does "mass assignment" actually happen in my code?

It usually happens when you're trying to be efficient. You've got a User profile page where a user can update their bio and location. Instead of writing a line for every single field, you might be tempted to just loop through the $_POST array and update the database record directly.

Here is a classic example of how I've seen developers shoot themselves in the foot:

// DANGER: This is a mass assignment vulnerability
$userId = $_SESSION['user_id'];
$updates = $_POST; // Just grabbing everything from the request

$sql = "UPDATE users SET ";
$sets = [];
foreach ($updates as $column => $value) {
    $sets[] = "$column = " . $db->quote($value);
}
$sql .= implode(', ', $sets) . " WHERE id = " . (int)$userId;
$db->query($sql);

On the surface, this looks clever. But here's the problem: the code doesn't care if those fields were actually on your HTML form. An attacker doesn't have to use your form; they can use a tool like Postman or curl to send a request containing is_admin=1. Since your loop blindly accepts everything in $_POST, they've just promoted themselves to administrator.

Can't I just trust the fields I put in my HTML form?

Absolutely not. This is a common mistake when you're first starting out. You have to remember that the browser is essentially a suggestion engine. A user can right-click "Inspect Element" and add a new input field to your form in two seconds, or they can bypass the browser entirely and send a raw HTTP request to your server.

If your PHP code assumes that the only data arriving is the data you provided in the <form> tag, you're leaving the door wide open. Never trust the client. I always tell my juniors: assume the user is actively trying to break your logic and bypass your constraints.

What's the best way to stop this without writing 50 lines of manual assignments?

The gold standard here is "Allow-listing." Instead of trying to block the "bad" fields (which is a losing battle because you'll eventually forget one), you define a strict list of fields that are allowed to be mass-assigned.

I usually handle this by creating a small filter array. Check out the difference here:

$userId = $_SESSION['user_id'];
$requestData = $_POST;

// Define exactly what the user is allowed to change
$allowedFields = ['bio', 'location', 'display_name'];
$filteredData = [];

foreach ($allowedFields as $field) {
    if (isset($requestData[$field])) {
        $filteredData[$field] = $requestData[$field];
    }
}

// Now we only loop through the filtered, safe data
$sets = [];
foreach ($filteredData as $column => $value) {
    $sets[] = "$column = " . $db->quote($value);
}

if (!empty($sets)) {
    $sql = "UPDATE users SET " . implode(', ', $sets) . " WHERE id = " . (int)$userId;
    $db->query($sql);
}

By doing this, even if an attacker sends is_admin=1 or account_balance=99999, those keys simply never make it into the $filteredData array. They are ignored entirely. It's a few extra lines of code, but it's the only way to sleep soundly at night.




📋 Practical Task

Fixing the Vulnerable Account Settings Page

You have been handed a legacy script for an account settings page. The current code uses a foreach loop to update the settings table based on the $_POST data, but it's currently vulnerable to mass assignment. An attacker has discovered they can change their subscription_plan to "premium" for free by injecting the field into the request.

Your Task: Rewrite the update logic to implement an allow-list. Ensure that only theme_color, email_notifications, and timezone can be updated via this request. Any other fields sent in the $_POST array must be ignored.

// VULNERABLE CODE TO FIX:
$userId = $_SESSION['user_id'];
$data = $_POST; 

$updates = [];
foreach ($data as $key => $val) {
    $updates[] = "$key = '" . mysqli_real_escape_string($conn, $val) . "'";
}

$sql = "UPDATE settings SET " . implode(', ', $updates) . " WHERE user_id = $userId";
mysqli_query($conn, $sql);
Rating
0 0

There are no comments for now.

to be the first to leave a comment.