Insecure Deserialization in Axum with Mutual Tls
Insecure Deserialization in Axum with Mutual Tls
Insecure deserialization involves reconstructing objects from untrusted data, which can lead to remote code execution or logic manipulation. In an Axum service that uses mutual TLS (mTLS), the assumption is that only authenticated clients with valid certificates can communicate with the server. However, mTLS authenticates the connection and does not inherently validate the structure or safety of the serialized payloads exchanged over that encrypted channel. If an endpoint accepts serialized data formats such as JSON, MessagePack, or CBOR and deserializes them without strict schema enforcement, an attacker who possesses a valid client certificate can still supply malicious payloads.
Consider an Axum handler that uses serde_json::from_slice or similar deserialization calls on request bodies. Even with mTLS ensuring transport-layer identity, the deserialization path remains a classic injection surface: crafted payloads can trigger gadget chains via unsafe deserialization libraries (e.g., Java native deserialization patterns or unsafe Rust crates that interpret serialized metadata). Common frameworks like serde_json are safe when used with strict derive macros and untagged or carefully bounded enums, but if the application dynamically resolves types or uses #[serde(untagged)] without validation, attackers may exploit polymorphic deserialization to execute unintended logic. This maps to the Unsafe Consumption check in middleBrick’s 12 parallel scans, which flags risky deserialization patterns and references the OWASP API Top 10 and related CWE entries.
An mTLS-enabled Axum endpoint that also exposes an OpenAPI spec may inadvertently document a permissive request body schema, widening the implicit trust boundary. middleBrick’s OpenAPI/Swagger analysis resolves all $ref definitions and cross-references them with runtime findings. If the spec allows broad or ambiguous types and the runtime handler performs unchecked deserialization, the scan highlights this as a potential BFLA/Privilege Escalation or Injection vector. Even without agent-based instrumentation, the scanner can detect anomalies via crafted probes, ensuring that insecure deserialization practices paired with mTLS are surfaced as actionable findings with severity and remediation guidance.
Mutual Tls-Specific Remediation in Axum
Securing deserialization in Axum with mTLS requires tightening both the TLS configuration and the data handling logic. On the transport side, enforce strict client certificate validation and prefer Rustls for predictable cipher suites. On the application side, avoid generic deserialization of untrusted inputs; use strongly typed, validated structures and prefer serde’s derive-based deserialization with bounded enums. Below are concrete code examples for an Axum service with mTLS that mitigate insecure deserialization risks.
Example 1: Axum server with Rustls mTLS and strict deserialization
use axum::{routing::post, Router};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use tls_rustls::RustlsConfig;
use tower_http::trace::TraceLayer;
#[derive(Deserialize, Serialize, Debug, Clone)]
struct SafeRequest {
user_id: u64,
action: Action,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
enum Action {
Read,
Write,
}
#[tokio::main]
async fn main() {
let config = RustlsConfig::from_pem_file("ca.pem", "server.pem", "server.key")
.expect("Failed to load server certificates");
let app = Router::new()
.route("/submit", post(submit_handler))
.layer(TraceLayer::new_for_http());
let listener = tokio_rustls::TlsAcceptor::from(config).accept_incoming(tokio::net::TcpListener::bind("0.0.0.0:8443").await.unwrap());
axum::serve(listener, app).await.unwrap();
}
async fn submit_handler(body: String) -> String {
let parsed: SafeRequest = serde_json::from_str(&body)
.map_err(|e| format!("Invalid payload: {e}"))?;
format!("Received: {:?}", parsed)
}
Example 2: Enforcing mTLS client verification with hyper and Rustls
use axum::Server;
use hyper::{Body, Request, Response};
use hyper_rustls::TlsAcceptor;
use std::convert::Infallible;
use std::fs;
async fn verify_client_cert(req: Request<Body>) -> Result<Response<Body>, Infallible> {
// In practice, inspect req.extensions() for client certificate details
// and reject if missing or invalid
Ok(Response::new(Body::from("mTLS verified")))
}
#[tokio::main]
async fn main() {
let certs = fs::read("ca.pem").unwrap();
let mut builder = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() // replace with ClientAuth::require() for mTLS
.with_single_cert(vec![certs], rustls::PrivateKey(fs::read("server.key").unwrap()))
.unwrap();
builder.client_auth_root_subjects = /* load trusted client roots */;
let tls_acceptor = TlsAcceptor::from(Arc::new(builder));
// Use hyper with the tls_acceptor and route to verify_client_cert
}
These examples focus on structured deserialization with explicit enums and strict parsing, combined with properly configured mTLS. They avoid generic deserialization of untrusted formats and ensure that only expected data shapes are accepted. When paired with continuous scanning via the middleBrick CLI or GitHub Action, such practices reduce the attack surface associated with insecure deserialization in mTLS-protected Axum services.