MEDIUM denial of serviceaxummutual tls

Denial Of Service in Axum with Mutual Tls

Denial Of Service in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability

Mutual Transport Layer Security (mTLS) in Axum adds a strong identity guarantee by requiring both the client and server to present valid certificates. While mTLS reduces risk from unauthorized clients, it can introduce Denial of Service (DoS) conditions when the handshake or certificate validation logic interacts poorly with request handling, resource limits, or runtime behavior.

In Axum, mTLS is typically enforced via a TLS acceptor (such as with tower-rs/rustls or axum::Server::bind_rustls) that completes the handshake before requests are handed to the application. If certificate validation is synchronous, CPU-intensive, or performs blocking I/O (for example, checking revocation via OCSP or doing online certificate lookup), the TLS acceptor thread can become a bottleneck. Under sustained connection attempts, this can exhaust worker threads or the bounded connection queue, causing legitimate requests to time out or be refused.

Additionally, mTLS enriches each request with peer identity (often mapped into extensions for downstream use). If the application or middleware eagerly validates certificates beyond the handshake (for example, re-checking revocation for every request) or performs expensive operations on the certificate data per request, the per-request cost increases. Combined with a high rate of short-lived connections (a common DoS pattern), this can saturate CPU and memory, leading to service unavailability for benign clients.

Another vector specific to mTLS is the size and complexity of the certificate chain. Large chains or many intermediate CAs increase handshake latency and memory use per connection. If Axum or the underlying acceptor does not enforce sensible limits on certificate chain size or header counts, an attacker can craft connections with oversized certificates to consume memory and trigger DoS. Rate limiting applied only at the HTTP layer may be bypassed if the resource exhaustion occurs earlier in the TLS handshake or connection acceptance stage.

Mutual Tls-Specific Remediation in Axum — concrete code fixes

Remediation focuses on reducing handshake cost, bounding resource usage, and ensuring validation does not block the request path. Prefer asynchronous, non-blocking certificate validation and avoid per-request expensive checks. Use connection and rate limits early, and enforce sensible certificate constraints.

Example: Configure Axum with rustls in non-blocking mode and set certificate and connection limits.

use axum::Router;
use rustls::{Certificate, PrivateKey, ServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::fs::File;
use std::io::BufReader;
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tower_http::trace::TraceLayer;

async fn build_server() -> Result<Router, Box<dyn std::error::Error + Send + Sync>> {
    // Load cert and key
    let cert_file = &mut BufReader::new(File::open("cert.pem")?);
    let key_file = &mut BufReader::new(File::open("key.pem")?);
    let cert_chain: Vec<Certificate> = certs(cert_file)?.into_iter().map(Certificate).collect();
    let mut keys: Vec<PrivateKey> = pkcs8_private_keys(key_file)?.into_iter().map(PrivateKey).collect();

    // Configure rustls with safe options
    let mut config = ServerConfig::builder()
        .with_safe_defaults()
        .with_client_cert_verifier(std::sync::Arc::new(
            // Example: a simple verifier that checks the cert is issued by a known CA
            rustls::server::AllowAnyAuthenticatedClient::new(vec![/* trusted CA certs */]),
        ))
        .with_single_cert(cert_chain, keys.remove(0))?;

    // Limit session tickets and buffer sizes to reduce DoS surface
    config.session_storage = std::sync::Arc::new(rustls::server::ServerSessionStorage::default());

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    let listener = TcpListener::bind(&addr).await?;
    let app = Router::new()
        .route("/", axum::routing::get(|| async { "ok" }))
        .layer(TraceLayer::new_for_http());

    axum::Server::from_tcp_rustls(listener, config)
        .unwrap()
        .serve(app.into_make_service())
        .await?
        .map_err(Into::into)
}

Key practices:

  • Use asynchronous, non-blocking certificate verification to avoid tying up worker threads.
  • Set limits on certificate chain depth and size at the TLS layer to prevent memory exhaustion from oversized chains.
  • Apply rate limiting and connection caps early (e.g., in the TCP listener or acceptor) to stop floods before they consume expensive handshake resources.
  • Cache validation results where safe (for example, short-lived OCSP stapled responses) to avoid repeated remote calls per request.
  • Monitor TLS handshake duration and connection counts to detect abnormal patterns that may indicate an active DoS attempt.

Related CWEs: resourceConsumption

CWE IDNameSeverity
CWE-400Uncontrolled Resource Consumption HIGH
CWE-770Allocation of Resources Without Limits MEDIUM
CWE-799Improper Control of Interaction Frequency MEDIUM
CWE-835Infinite Loop HIGH
CWE-1050Excessive Platform Resource Consumption MEDIUM

Frequently Asked Questions

Does mTLS always make an API more vulnerable to DoS?
No. mTLS itself does not introduce DoS, but the implementation choices (synchronous validation, large chains, missing limits) can make the handshake path a bottleneck. Proper configuration and limits mitigate the added risk.
Can rate limiting alone protect against mTLS-related DoS?
Rate limiting at the HTTP layer may not stop resource exhaustion during the TLS handshake, which occurs before HTTP routing. Apply connection caps and handshake-level limits to defend against TLS-layer DoS.