Deserialization Attack in Actix
How Deserialization Attack Manifests in Actix
Actix-web, a popular Rust framework for building APIs, relies heavily on serde for deserializing incoming request data (JSON, form data, etc.) into Rust structs. A deserialization attack occurs when an attacker supplies maliciously crafted input that, during the deserialization process, triggers unintended behavior. While Rust's type system and serde's design mitigate many classic deserialization vulnerabilities (like those seen in Java or .NET), specific patterns in Actix applications can still lead to security issues.
The primary risk arises from unvalidated deserialization of complex, nested types. Consider an Actix handler that accepts a JSON payload directly into a struct containing Vec<T>, HashMap<K, V>, or custom enums. An attacker can send extremely large or deeply nested JSON structures. During deserialization, this can cause:
- Denial of Service (DoS): Excessive memory allocation or stack overflow from deep recursion. For example, a JSON array with tens of thousands of elements or an object nested hundreds of levels deep can consume significant resources, potentially crashing the service.
- Logic Flaws via Unexpected Types: If a struct field expects a simple
Stringbut the deserializer is permissive (e.g., usingdeserialize_anyor a loosely defined enum), an attacker might supply a number, boolean, or even a nested object. Application logic that assumes aStringmay panic (causing a DoS) or behave incorrectly when processing a different type. - Unexpected Enum Variant Activation: If an API uses an enum to represent actions (e.g.,
enum Action { Delete, Update }) and deserializes directly from user input without restricting allowed variants, an attacker might supply a variant not handled by the application logic, leading to unhandled enum errors or default behaviors that bypass authorization checks.
Here is a vulnerable Actix handler pattern:
use actix_web::{post, web, HttpResponse, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUserRequest {
username: String,
roles: Vec<String>, // Attacker can send thousands of roles
metadata: serde_json::Value, // Permissive type, accepts any JSON
}
#[post("/users")]
async fn create_user(user: web::Json<CreateUserRequest>) -> impl Responder {
// Logic processes user.roles and user.metadata without size/depth checks
HttpResponse::Ok().finish()
}An attacker could send a payload with a roles array containing 10,000 elements or a metadata object nested 500 levels deep, exhausting server resources during deserialization before the application logic even executes.
Actix-Specific Detection
Detecting deserialization vulnerabilities in an Actix API requires testing the endpoint's response to maliciously structured input. As a black-box scanner, middleBrick's Input Validation check probes endpoints by sending payloads designed to trigger resource exhaustion or type confusion. It monitors for:
- Error messages that indicate deserialization failure (e.g.,
"invalid type: sequence, expected a string"or"depth limit exceeded"). - Response timeouts or high latency suggesting the server is struggling to process the payload.
- HTTP 500 errors caused by panics during deserialization (e.g.,
"index out of bounds"from overly large arrays). - Unexpected HTTP status codes that differ from the normal error handling for valid but incorrect input.
middleBrick automates this detection. When you scan an Actix API endpoint (e.g., https://api.example.com/users) via the web dashboard or CLI (middlebrick scan https://api.example.com/users), it sends a series of test payloads targeting common deserialization pitfalls. The scanner correlates any anomalous responses with the endpoint's OpenAPI/Swagger specification (if available) to confirm the parameter is indeed deserialized from the request body.
For example, if the OpenAPI spec defines a CreateUserRequest with a roles: array[string] field, middleBrick will send a payload with an array of 10,000 strings. A slow response or 500 error would be flagged as a potential deserialization DoS risk. The resulting report includes a severity score (e.g., High) and remediation guidance specific to Actix/serde.
Actix-Specific Remediation
Remediation in Actix focuses on strictly controlling what data is accepted during deserialization and adding explicit validation. The goal is to reject malicious payloads before they consume excessive resources or cause logic errors.
1. Limit Collection Sizes and Nesting Depth
Use serde's deserialize_with attribute or custom deserializers to enforce limits on Vec, HashMap, and nested structures. The serde_json crate offers a Deserializer with built-in limits:
use actix_web::{post, web, HttpResponse, Responder};
use serde::{de, Deserialize, Deserializer};
use std::fmt;
const MAX_ARRAY_LEN: usize = 100;
const MAX_DEPTH: usize = 32;
struct LenLimitedVec<T>(Vec<T>);
impl<'de, T> Deserialize<'de> for LenLimitedVec<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let vec = Vec::deserialize(deserializer)?;
if vec.len() > MAX_ARRAY_LEN {
Err(de::Error::custom(format!("array length exceeds limit of {}", MAX_ARRAY_LEN)))
} else {
Ok(LenLimitedVec(vec))
}
}
}
#[derive(Deserialize)]
struct CreateUserRequest {
username: String,
#[serde(default)]
roles: LenLimitedVec<String>, // Now limited to 100 items
#[serde(deserialize_with = "deserialize_metadata")]
metadata: serde_json::Value, // Will be depth-limited
}
fn deserialize_metadata<'de, D>(deserializer: D) -> Result<serde_json::Value, D::Error>
where
D: Deserializer<'de>,
{
let mut de = serde_json::Deserializer::from_reader(deserializer);
de.disable_recursion_limit(); // Or set a custom limit if needed
// Note: serde_json has a default recursion limit (128). For stricter control,
// consider using a custom visitor that tracks depth.
serde_json::Value::deserialize(&mut de)
.map_err(|e| de::Error::custom(e.to_string()))
}
#[post("/users")]
async fn create_user(user: web::Json<CreateUserRequest>) -> impl Responder {
HttpResponse::Ok().finish()
}2. Restrict Allowed Enum Variants and Types
When deserializing enums, use #[serde(rename_all = "...")] and avoid deserialize_any. For fields that should be strings, ensure the struct field is typed as String and not a generic serde_json::Value. If you must accept flexible data, validate its shape after deserialization.
3. Validate with the validator Crate
Combine deserialization limits with runtime validation using the validator crate, which integrates with Actix's web::Json<T> extractor automatically if T implements the Validate trait.
use validator::{Validate, ValidationError};
#[derive(Deserialize, Validate)]
struct CreateUserRequest {
#[validate(length(min = 1, max = 50))]
username: String,
#[serde(default)]
roles: LenLimitedVec<String>,
#[validate(custom = "validate_metadata")]
metadata: serde_json::Value,
}
fn validate_metadata(value: &serde_json::Value) -> Result<(), ValidationError> {
// Check depth, key counts, etc.
if let serde_json::Value::Object(map) = value {
if map.len() > 20 {
return Err(ValidationError::new("metadata object too large"));
}
}
Ok(())
}
// Actix will automatically reject requests that fail validation
#[post("/users")]
async fn create_user(user: web::Json<CreateUserRequest>) -> impl Responder {
// At this point, user.0 is guaranteed to pass validation
HttpResponse::Ok().finish()
}4. Set Global Deserialization Limits in Actix
Configure Actix's JsonConfig to set limits on payload size and recursion depth for all JSON endpoints:
use actix_web::{App, HttpServer, web};
use actix_web::web::JsonConfig;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.app_data(JsonConfig::default()
.limit(4096) // Max JSON payload size in bytes
.error_handler(|err, _req| {
// Custom error response
actix_web::HttpResponse::BadRequest().json(serde_json::json!({
"error": "Invalid JSON payload"
}))
})
)
.service(create_user)
})
.bind("127.0.0.1:8080")?
.run()
.await
}By combining these techniques—strict deserialization, explicit validation, and global limits—you can effectively mitigate deserialization attacks in Actix applications. middleBrick's scan report will highlight any endpoints lacking these protections and provide remediation steps tailored to Actix and serde.