Mass Assignment in Axum
How Mass Assignment Manifests in Axum
Mass assignment vulnerabilities in Axum typically arise when request data is automatically deserialized into structs without proper field-level access control. In Axum, this commonly occurs when using extractors like Json<T> or Form<T> to bind HTTP request bodies to Rust structs.
The vulnerability manifests when an attacker can supply values for struct fields that should be immutable or restricted. For example, consider a user update endpoint where the request struct includes both user-modifiable and admin-only fields:
#[derive(Deserialize)]
struct UpdateUserRequest {
email: String,
username: String,
is_admin: bool, // Should be immutable by users
account_balance: f64, // Should be immutable by users
}
async fn update_user(
Json(payload): Json<UpdateUserRequest>,
user_id: Path<Uuid>,
) -> impl IntoResponse {
// Payload can set is_admin and account_balance!
let updated = update_user_in_db(user_id, payload).await;
Json(updated)
}
In this Axum handler, an attacker can modify is_admin and account_balance fields through the JSON payload, even though these should be restricted to administrator operations. This is a classic mass assignment flaw where the framework's convenient deserialization becomes a security liability.
Another Axum-specific manifestation occurs with Path and Query extractors when combined with struct updates. An attacker might manipulate URL parameters to affect fields that should be protected:
#[derive(Deserialize)]
struct UserUpdateParams {
id: Uuid,
email: String,
role: String, // Should be admin-only
}
async fn update_user_params(
Query(params): Query<UserUpdateParams>,
) -> impl IntoResponse {
// role can be set via query string!
let updated = update_user_in_db(params.id, params).await;
Json(updated)
}
Even Axum's middleware and extension mechanisms can introduce mass assignment risks when request-scoped data is merged with user input without validation. The combination of Rust's type safety with Axum's ergonomic extractors creates a false sense of security—developers assume the compiler prevents these issues, but the problem is at the application logic level.
Axum-Specific Detection
Detecting mass assignment vulnerabilities in Axum requires examining how request extractors map to data models. The first step is identifying all Json<T>, Form<T>, Query<T>, and Path<T> extractors in your codebase and analyzing their target structs.
Look for these specific patterns in your Axum handlers:
use axum::{extract::{Json, Form, Query, Path}, routing::post, Router};
// High-risk patterns to identify:
fn find_mass_assignment_risks() {
// 1. Structs with admin/mutable mixed fields
// 2. Missing #[serde(skip_deserializing)] on sensitive fields
// 3. Default values overridden by user input
// 4. ID fields that can be manipulated
}
middleBrick's Axum-specific scanning analyzes your API endpoints by examining the OpenAPI/Swagger specification alongside runtime behavior. It identifies endpoints where struct deserialization could lead to privilege escalation by:
- Mapping request schemas to data models and flagging fields with elevated privileges
- Testing whether admin-only fields can be set through normal user requests
- Checking for missing field-level authorization controls
- Verifying that sensitive fields like
is_admin,role,permissions, and financial values aren't exposed to mass assignment
For example, middleBrick would flag this vulnerable Axum endpoint:
#[derive(Deserialize)]
struct UserUpdate {
email: String,
username: String,
is_admin: bool, // Critical field exposed to mass assignment
account_balance: f64, // Financial data exposed to tampering
}
// middleBrick detects this as a BOLA/IDOR vulnerability
The scanner tests these endpoints by sending crafted requests attempting to set protected fields, then analyzes whether the backend accepts these modifications. This active testing approach reveals vulnerabilities that static analysis might miss, particularly in complex Axum applications with multiple extractors and middleware layers.
Axum-Specific Remediation
Remediating mass assignment vulnerabilities in Axum requires a defense-in-depth approach using Axum's native features and Rust's type system. The most effective strategy is field-level separation between user-modifiable and admin-only data.
First, create distinct structs for different privilege levels:
// User-modifiable fields only
#[derive(Deserialize)]
struct UserSelfUpdate {
email: String,
username: String,
// Sensitive fields omitted entirely
}
// Admin-modifiable fields (separate endpoint)
#[derive(Deserialize)]
struct AdminUserUpdate {
email: String,
username: String,
is_admin: bool,
account_balance: f64,
role: String,
}
// Handler for users updating themselves
async fn user_update_self(
Json(payload): Json<UserSelfUpdate>,
user_id: Path<Uuid>,
auth_user: AuthenticatedUser, // From middleware
) -> impl IntoResponse {
if auth_user.id != user_id {
return StatusCode::FORBIDDEN;
}
let updated = update_user_in_db(user_id, payload).await;
Json(updated)
}
// Separate handler for admin operations
async fn admin_update_user(
Json(payload): Json<AdminUserUpdate>,
user_id: Path<Uuid>,
auth_user: AuthenticatedUser,
) -> impl IntoResponse {
if !auth_user.is_admin {
return StatusCode::FORBIDDEN;
}
let updated = update_user_in_db(user_id, payload).await;
Json(updated)
}
Second, use #[serde(skip_deserializing)] to prevent sensitive fields from being set via JSON:
#[derive(Deserialize)]
struct UpdateRequest {
email: String,
username: String,
#[serde(skip_deserializing)]
is_admin: bool, // Cannot be set via JSON
#[serde(skip_deserializing)]
account_balance: f64,
}
// These fields must be set explicitly in code
async fn update_user(
Json(mut payload): Json<UpdateRequest>,
user_id: Path<Uuid>,
auth_user: AuthenticatedUser,
) -> impl IntoResponse {
// Only authorized users can modify certain fields
if auth_user.is_admin {
payload.is_admin = get_admin_status_from_db(user_id).await;
payload.account_balance = get_balance_from_db(user_id).await;
}
let updated = update_user_in_db(user_id, payload).await;
Json(updated)
}
Third, implement Axum middleware for field-level authorization:
use axum::{extract::Extension, http::StatusCode, middleware::Next, Request, Response};
use axum::middleware::Next;
// Custom middleware to filter sensitive fields
async fn field_authorization_middleware(
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
// Check if request targets sensitive endpoints
let path = req.uri().path();
if path.contains("/admin/") || path.contains("/privileged/") {
// For admin endpoints, allow all fields
return Ok(next.run(req).await);
}
// For user endpoints, filter out sensitive fields
let mut payload = req.extensions_mut().get_mut::<JsonPayload>().cloned();
if let Some(mut payload) = payload {
// Remove sensitive fields from payload
payload.remove_sensitive_fields();
req.extensions_mut().insert(payload);
}
Ok(next.run(req).await)
}
Finally, integrate middleBrick into your CI/CD pipeline to continuously scan for mass assignment vulnerabilities as your Axum application evolves:
# GitHub Action for Axum security scanning
- name: Scan Axum API for mass assignment
uses: middlebrick/middlebrick-action@v1
with:
api_url: ${{ secrets.API_URL }}
scan_type: "security"
fail_if_score_below: 80
env:
MIDDLEBRICK_API_KEY: ${{ secrets.MIDDLEBRICK_API_KEY }}
This combination of architectural separation, field-level controls, and continuous scanning with middleBrick provides comprehensive protection against mass assignment vulnerabilities in Axum applications.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |