HIGH axummass assignment exploit

Mass Assignment Exploit in Axum

How Mass Assignment Exploit Manifests in Axum

Mass assignment, also known as auto-binding, is a vulnerability that occurs when an application automatically maps user-supplied input to object properties without proper filtering. In Axum, this commonly happens when using Json<T> extractors with structs that contain fields which should not be modifiable by the client. Attackers can exploit this by including additional fields in their JSON payload to alter sensitive properties like is_admin, role, or balance.

Consider a typical user update endpoint in Axum:

use axum::{Json, extract::Path};
use serde::Deserialize;
use uuid::Uuid;

#[derive(Deserialize)]
struct UserUpdate {
id: Uuid,
email: String,
is_admin: bool, // This field should not be user-controlled!
}

async fn update_user(Json(payload): Json<UserUpdate>) -> impl IntoResponse {
// Naive implementation: update all fields from payload
sqlx::query!(
"UPDATE users SET email = $1, is_admin = $2 WHERE id = $3",
payload.email,
payload.is_admin,
payload.id
)
.execute(&pool)
.await?;
// ...
}

In this example, the UserUpdate struct includes is_admin. An attacker can send a request like:

{
"id": "123e4567-e89b-12d3-a456-426614174000",
"email": "[email protected]",
"is_admin": true
}

The backend will blindly set is_admin to true, granting administrative privileges. This is a classic mass assignment vulnerability. The issue is exacerbated when the struct is reused for both creation and update operations, or when the same struct is used across multiple endpoints without considering the principle of least privilege for each action.

Axum's reliance on serde for deserialization means that any field present in the struct and provided in the JSON will be bound. By default, serde ignores unknown fields, but that does not prevent mass assignment if the struct itself contains sensitive fields. The vulnerability stems from the design of the data transfer object (DTO), not from deserialization behavior.

Axum-Specific Detection

Detecting mass assignment in Axum applications requires careful code review and dynamic testing. Statically, look for handlers that accept JSON bodies deserialized into structs containing sensitive fields (e.g., is_admin, role, permissions, price, balance) and then use those structs to update database records without filtering. Pay special attention to UPDATE queries that set all columns from the deserialized struct, or ORM methods that save the entire object (e.g., diesel::update(...).set(&payload) if payload is the entire struct).

Dynamic testing involves sending requests with extra fields and observing the effect. For example, probe the /users/{id} endpoint with a payload that includes is_admin. If the response returns the updated user object and includes is_admin: true, or if subsequent requests behave as an admin, the vulnerability is confirmed.

middleBrick automates this detection. When you scan an Axum API endpoint, middleBrick's Property Authorization check sends a series of probes, including attempts to set common sensitive fields. It analyzes the response for indications that the field was accepted (e.g., by checking if the field appears in the response or if the behavior changes). The scanner then reports a finding with severity and remediation guidance, mapped to OWASP API Top 10: API07:2023 – Broken Object Property Authorization.

For instance, scanning an Axum API with an endpoint like PUT /users/123 might yield a middleBrick report:

Finding: Property Authorization
Severity: High
Endpoint: PUT /users/{id}
Description: The endpoint accepts the 'is_admin' property, allowing privilege escalation.
Evidence: Request with {"is_admin": true} returned 200 and the response included "is_admin": true.
Remediation: Use a dedicated DTO that excludes sensitive fields.

This automated approach is faster and more consistent than manual testing, especially for large APIs.

Axum-Specific Remediation

Remediating mass assignment in Axum involves ensuring that only intended properties are accepted from the client. The most effective approach is to use separate, minimal DTOs for each operation. For user updates, define a struct that includes only the fields that users are allowed to change:

#[derive(Deserialize)]
struct UserUpdateRequest {
email: Option<String>,
name: Option<String>,
// No is_admin, no role, etc.
}

Then, in the handler, apply these fields selectively:

async fn update_user(
Path(id): Path<Uuid>,
Json(payload): Json<UserUpdateRequest>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let mut user = fetch_user(id).await.map_err(|e| (StatusCode::NotFound, e.to_string()))?;

if let Some(email) = payload.email {
user.email = email;
}
if let Some(name) = payload.name {
user.name = name;
}

save_user(&user).await?;
Ok(Json(user))
}

This pattern ensures that even if an attacker sends is_admin in the JSON, it will be ignored because the struct does not have that field. By default, serde will ignore unknown fields, so the extra field is safely discarded. For stricter security, you can add #[serde(deny_unknown_fields)] to the DTO to reject requests containing any unexpected fields, providing an additional layer of defense.

When using an ORM like Diesel, avoid passing the entire DTO to the update statement. Instead, construct the update query with only the allowed columns:

use diesel::prelude::*;

let mut update_stmt = users::table.filter(users::id.eq(id)).into_update::<&User>();
if let Some(email) = &payload.email {
update_stmt = update_stmt.set(users::email.eq(email));
}
if let Some(name) = &payload.name {
update_stmt = update_stmt.set(users::name.eq(name));
}
// Execute update_stmt...

This explicit approach prevents accidental inclusion of sensitive fields. Additionally, validate and sanitize input values where appropriate (e.g., email format).

After implementing fixes, rescan the API with middleBrick to verify that the mass assignment finding is resolved. The Property Authorization check should no longer report the issue, and your security score will improve.

Frequently Asked Questions

Can mass assignment occur in Axum if I use Option fields in my DTO?
Yes, if the DTO includes sensitive fields as Option (e.g., is_admin: Option). An attacker can still set those fields by providing a value. The fix is to exclude sensitive fields entirely from the DTO.
Does middleBrick require credentials to scan my Axum API?
No, middleBrick performs unauthenticated black-box scanning. You simply provide the endpoint URL, and it tests the publicly accessible attack surface. For APIs that require authentication, you can optionally provide credentials, but it's not required for basic scans.