Insecure Deserialization in Axum with Api Keys
Insecure Deserialization in Axum with Api Keys
Insecure deserialization in an Axum service that uses API keys can occur when an endpoint accepts serialized objects from the request and reconstructs them without strict validation. Axum does not perform deserialization automatically; developers add it by choosing a format such as bincode, rmp-serde, or custom codecs and applying them in extractors. If the API key is conveyed in a header but the request body is still deserialized with unsafe settings, an attacker can embed malicious payloads that execute code or alter behavior when the application processes the deserialized data.
Consider an Axum handler that uses tower_http::classification::ClassifyRequest only for routing and then deserializes a user-supplied payload with bincode in an extractor. The API key is validated beforehand, but the deserialization step is not sandboxed. A crafted payload can trigger gadgets present in the runtime (e.g., collections, filesystem paths, or network-related structs) leading to remote code execution or unauthorized state changes. This is an application-level issue rather than a flaw in Axum itself, but the framework’s flexibility in choosing extractors means developers must explicitly enforce safe deserialization practices.
Framework choices matter. If you use serde_bytes or custom deserialize implementations, ensure you reject unexpected tags and enforce strict type boundaries. The API key remains a strong authentication signal, but it does not protect the deserialization path itself. Attackers who discover an endpoint that processes untrusted serialized input can exploit this regardless of the presence of a valid key. Therefore, treat deserialization as an untrusted input operation even when authentication is in place.
Api Keys-Specific Remediation in Axum
Remediation focuses on avoiding unsafe deserialization and hardening how API keys are handled and validated. Prefer strongly typed, schema-driven formats such as JSON with strict schemas or Protocol Buffers with generated Rust types. Avoid formats that allow arbitrary object graphs unless you implement a tightly controlled deserializer with type whitelisting.
Use explicit extractors instead of generic ones. For example, parse the API key in a dedicated extractor and keep deserialization separate and constrained:
use axum::{async_trait, extract::FromRequest, http::Request, response::IntoResponse};
use std::convert::Infallible;
struct ApiKey(String);
#[async_trait]
impl FromRequest<S> for ApiKey
where
S: Send + Sync,
{
type Rejection = (axum::http::StatusCode, String);
async fn from_request(req: Request<_>, _: &S) -> Result<Self, Self::Rejection> {
let key = req.headers()
.get("X-API-Key")
.and_then(|v| v.to_str().ok())
.map(String::from)
.ok_or_else(|| (axum::http::StatusCode::UNAUTHORIZED, "Missing API key".to_string()))?;
// Add additional checks: key format, allow-list, rate limiting by key, etc.
if VALID_KEYS.contains(&key.as_str()) {
Ok(ApiKey(key))
} else {
Err((axum::http::StatusCode::UNAUTHORIZED, "Invalid API key".to_string()))
}
}
}
// Handler that requires ApiKey and receives a strongly typed body
async fn create_item(
_key: ApiKey,
body: String, // keep payload as raw String first
) -> impl IntoResponse {
// Validate and parse body with a strict schema; avoid unsafe deserialization
// e.g., use serde_json::from_str with a defined struct
"OK"
}
If you must deserialize structured data, use serde with denormalized and explicit types, and avoid enabling features that allow arbitrary visitor patterns. With bincode, prefer fixed-size configurations and do not rely on the default configuration that may enable dangerous options:
use bincode::{config, Config};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct MyData {
id: u64,
name: String,
}
let my_config = config::standard().with_fixed_int_encoding();
let bytes: Vec<u8> = bincode::encode_to_vec(&MyData { id: 1, name: "test".into() }, my_config).unwrap();
let decoded: MyData = bincode::decode_from_slice(&bytes, my_config).map_err(|e| e.to_string())?.0;
Additionally, apply runtime checks on deserialized fields, enforce size limits, and validate the API key before using any deserialized content. Combine these practices with continuous scanning using tools like middleBrick to detect insecure configurations in Axum-based APIs.