Mass Assignment in Actix
How Mass Assignment Manifests in Actix
Mass assignment vulnerabilities in Actix typically occur when user-supplied data is automatically bound to struct fields without proper validation or filtering. In Actix, this commonly happens through the serde::Deserialize trait implementation on request bodies.
use actix_web::{web, App, HttpServer, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct UpdateUser {
id: i32,
name: String,
email: String,
is_admin: bool, // Critical field
password: String,
}
async fn update_user(
path: web::Path<i32>,
json: web::Json<UpdateUser>,
) -> impl Responder {
let user_id = path.into_inner();
let update = json.into_inner();
// Vulnerable: client can set any field including is_admin
if user_id == update.id {
// Database update with all fields from client
update_user_in_db(update).await;
}
web::Json({"status": "ok"})
}
The vulnerability here is that an attacker can modify the is_admin field to elevate privileges, or set password to lock out the legitimate user. Actix's automatic deserialization makes it easy to accidentally expose sensitive fields.
Another Actix-specific manifestation occurs with query parameters and path variables:
#[derive(Deserialize)]
struct QueryParams {
user_id: i32,
role: String, // Should be validated
limit: i32,
}
async fn search_users(
query: web::Query<QueryParams>,
) -> impl Responder {
let params = query.into_inner();
// Vulnerable: role could be manipulated to bypass authorization
let results = search_db(params.user_id, params.role).await;
web::Json(results)
}
Actix's strong typing and automatic deserialization make it particularly important to be explicit about which fields should be accepted from untrusted sources.
Actix-Specific Detection
Detecting mass assignment vulnerabilities in Actix applications requires examining both the data flow and the deserialization patterns. middleBrick's black-box scanning approach is particularly effective for Actix APIs because it tests the actual runtime behavior without needing source code access.
When scanning an Actix API endpoint, middleBrick tests for mass assignment by:
- Submitting payloads with modified field values to detect if sensitive fields are writable
- Testing for BOLA (Broken Object Level Authorization) patterns where object IDs can be manipulated
- Checking for privilege escalation by attempting to modify admin/role fields
- Verifying input validation boundaries to ensure fields are properly constrained
For Actix applications, middleBrick's scanning process includes specific checks for:
// Example of what middleBrick tests for:
POST /api/users/123 HTTP/1.1
Content-Type: application/json
{
"id": 123,
"name": "test",
"email": "test@example.com",
"is_admin": true, // Attempt privilege escalation
"password": "hacked", // Attempt credential manipulation
"internal_flag": true // Test for unknown fields
}
The scanner also examines OpenAPI specifications for Actix applications to identify potential mass assignment vectors by analyzing the schema definitions and endpoint parameters.
middleBrick's LLM security checks are particularly relevant for Actix applications that integrate AI features, as prompt injection attacks can sometimes be combined with mass assignment vulnerabilities to achieve more sophisticated exploits.
Actix-Specific Remediation
Actix provides several native approaches to prevent mass assignment vulnerabilities. The most effective pattern is to use explicit field selection rather than automatic deserialization of entire structs.
use actix_web::{web, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct UpdateUserRequest {
name: Option<String>,
email: Option<String>,
// No is_admin field - cannot be set by client
}
async fn update_user(
path: web::Path<i32>,
json: web::Json<UpdateUserRequest>,
) -> impl Responder {
let user_id = path.into_inner();
let update = json.into_inner();
// Only allow specific fields to be updated
let mut user = get_user_from_db(user_id).await?;
if let Some(name) = update.name {
user.name = name;
}
if let Some(email) = update.email {
user.email = email;
}
// is_admin field remains unchanged
update_user_in_db(&user).await;
web::Json({"status": "ok"})
}
Another Actix-specific approach uses the #[serde(skip)] attribute to exclude sensitive fields from deserialization:
#[derive(Deserialize)]
struct UserUpdate {
name: String,
email: String,
#[serde(skip)]
is_admin: bool, // Cannot be set via JSON deserialization
#[serde(skip)]
password_hash: String,
}
For Actix applications using query parameters, explicit validation is critical:
use actix_web::{web, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct SearchParams {
user_id: i32,
#[serde(default = "default_limit")]
limit: i32,
}
fn default_limit() -> i32 { 10 }
async fn search_users(
query: web::Query<SearchParams>,
) -> impl Responder {
let params = query.into_inner();
// Validate role-based access
if !has_permission(params.user_id, "search") {
return HttpResponse::Forbidden().finish();
}
let results = search_db(params.user_id, params.limit).await;
web::Json(results)
}
The key principle in Actix is to be explicit about what can be modified, rather than relying on automatic field binding for all request data.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |