Mass Assignment in Axum with Basic Auth
Mass Assignment in Axum with Basic Auth — how this specific combination creates or exposes the vulnerability
Mass Assignment occurs when an API binds HTTP request payload fields directly to internal data models without explicit allowlisting. In Axum, this commonly happens when developers use framework features such as form extraction or JSON extract with structs that contain more fields than intended to be user-settable. When Basic Auth is used for authentication, the presence of a static credential pair (username and password) can create a false sense of security, leading developers to assume that because requests are authenticated, mass assignment is not a concern. This assumption is dangerous.
Consider an endpoint that updates a user profile. If the handler uses Json({..}) to bind the request body to a struct that includes an is_admin field, an authenticated user who knows the endpoint can modify that field and escalate privileges. Basic Auth does not restrict which fields can be set; it only provides a transport-layer identity assertion. The combination therefore exposes a clear path for privilege escalation (BOLA/IDOR and BFLA) because authentication is decoupled from authorization logic. Attackers can send crafted JSON payloads with unexpected keys, and Axum will silently populate fields that should be immutable or server-controlled, such as permissions, roles, or verification status.
Real-world attack patterns mirror findings from the OWASP API Top 10 and mapped checks in middleBrick scans. For example, an unauthenticated or authenticated probe might detect that a PATCH endpoint accepts a role field and successfully changes it, indicating Excessive Agency. Because middleBrick runs active prompt injection and system prompt leakage checks for LLM endpoints, it can also surface risks where AI-driven integrations inadvertently expose administrative operations through mass-assignable endpoints. Without explicit schema validation or allowlisting, the API surface remains wide open, regardless of how credentials are transmitted.
Basic Auth-Specific Remediation in Axum — concrete code fixes
Remediation centers on strict input validation and explicit allowlisting, independent of the authentication mechanism. Even when endpoints are protected by Basic Auth, handlers must not rely on trust derived from credentials. Instead, define separate models for incoming payloads and for database or domain models, and map only permitted fields explicitly.
Example: Safe handler with Basic Auth and explicit update model
use axum::{routing::patch, Router, extract::State, http::HeaderMap, extract::headers::Authorization, extract::Query, response::IntoResponse};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use tower_http::auth::{AuthLayer, credentials::BasicCredentials};
#[derive(Debug, Deserialize)]
struct UpdateUserPayload {
email: String,
display_name: String,
// Intentionally omit is_admin, role, verified_at, etc.
}
#[derive(Debug, Serialize)]
struct UserProfile {
id: u64,
email: String,
display_name: String,
is_admin: bool,
verified_at: Option,
}
async fn update_profile(
State(user_db): State<std::sync::Arc<std::sync::Mutex<Vec<UserProfile>>>>,
auth: Authorization<basic::Basic>,
Json(payload): Json<UpdateUserPayload>,
) -> impl IntoResponse {
let mut db = user_db.lock().unwrap();
if let Some(user) = db.iter_mut().find(|u| u.id == 1) { // simplified lookup
user.email = payload.email;
user.display_name = payload.display_name;
// Do NOT assign is_admin or other fields from payload
}
// Return a sanitized view
Json(serde_json::json!({
"id": 1,
"email": user.email,
"display_name": user.display_name,
"is_admin": user.is_admin,
}))
}
#[tokio::main]
async fn main() {
let user_db = std::sync::Arc::new(std::sync::Mutex::new(vec![
UserProfile { id: 1, email: "[email protected]".into(), display_name: "Test".into(), is_admin: false, verified_at: None },
]));
let app = Router::new()
.route("/profile", patch(update_profile))
.layer(AuthLayer::basic(verify_credentials))
.with_state(user_db);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
fn verify_credentials(creds: BasicCredentials) -> bool {
creds.password() == "secret"
}
Complementary practices
- Use serde with
deny_unknown_fieldson incoming structs to reject unexpected keys at the boundary. - Apply server-side authorization checks in handlers, even when Basic Auth is present, to enforce least privilege.
- Leverage middleBrick’s CLI (
middlebrick scan <url>) to validate that your endpoints do not accept mass-assignable fields and to confirm that authentication does not bypass schema validation.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |