Rate Limiting Bypass in Axum with Mutual Tls
Rate Limiting Bypass in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
Rate limiting is a control that restricts the number of requests a client can make over a time window. When mutual TLS (mTLS) is used in Axum, the server authenticates the client by requesting and validating a client certificate. If rate limiting is applied only after the TLS handshake completes, the boundary between authenticated and unauthenticated processing becomes relevant to bypass patterns. An attacker that can present a valid but low-privilege client certificate may still saturate per-client or per-endpoint limits, leading to denial-of-service for legitimate certificate holders or enabling enumeration via timing differences.
More specifically, a bypass can occur when rate limiting is implemented at the HTTP handler level but does not account for mTLS-verified identity early in the request lifecycle. For example, if a middleware checks the certificate after a per-IP or per-connector limit, an attacker with a valid certificate can open many connections that each present the certificate, exhausting server resources or evading intended request caps. Additionally, if different routes apply different limits and certificate fields (such as organization or common name) are not consistently mapped to a stable identifier, an attacker may route requests across endpoints to exploit inconsistent limit enforcement.
In an Axum service, the effective rate limiting boundary should be established as close as possible to connection acceptance and before expensive routing or business logic. This means integrating rate checks with the identity derived from mTLS verification, using a consistent principal (for example, the certificate subject or a mapped user ID), and ensuring that limits are applied uniformly across routes that share the same trust domain. Without this alignment, the combination of mTLS and rate limiting can expose subtle timing or identity-mapping weaknesses that an attacker can probe using authenticated requests.
Consider an Axum service that uses tower-http rate limiting and loads client certificates for mTLS. If the rate limiter is placed after the TLS layer but before proper identity normalization, an attacker with a valid certificate could open multiple connections to bypass per-user limits. The scanner tests this class of issue by sending requests that include valid client certificates while varying connection and request patterns to detect inconsistent limit enforcement across routes.
To detect such issues programmatically, scans include checks that validate whether rate limiting is enforced in a way that accounts for mTLS-verified identities and whether limits remain consistent across endpoints that share the same authentication scope. Findings highlight whether effective throttling is tied to the certificate identity or left to network-level approximations, and they provide remediation guidance to align limit boundaries with authenticated principals.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
Secure remediation centers on enforcing rate limits keyed to the mTLS-verified identity and ensuring that limits are applied before routing or expensive processing. In Axum, you can integrate tower_http rate limiting with a custom key generator that reads the client certificate from the request extensions populated by the TLS layer. This keeps the rate boundary aligned with authenticated principals and prevents bypass via connection-level pooling.
Below is a syntactically correct example of an Axum service with mTLS and per-user rate limiting using tower_http. The server requests client certificates, validates them against a trusted CA, extracts the subject as a stable identifier, and applies rate limits keyed by that identifier. Dependencies used include axum, tower_http, rustls, and tokio.
use axum::{
routing::get,
Router,
Extension,
http::Request,
};
use tower_http::{
trace::TraceLayer,
limit::{RateLimitLayer, DefaultKeyGenerator},
};
use std::sync::Arc;
use rustls::{ServerConfig, Certificate, PrivateKey};
use axum::extract::ConnectInfo;
use hyper::server::conn::AddrStream;
async fn handler() -> &'static str {
"ok"
}
#[tokio::main]
async fn main() {
// Load server certificate and key
let certs = rustls_pemfile::certs(&mut std::io::BufReader::new(std::fs::File::open("server-cert.pem").unwrap()))
.unwrap()
.into_iter()
.map(Certificate)
.collect::>();
let mut keys = rustls_pemfile::pkcs8_private_keys(&mut std::io::BufReader::new(std::fs::File::open("server-key.pem").unwrap()))
.unwrap()
.into_iter()
.map(PrivateKey)
.collect::>();
let mut server_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() // we will require client auth later via custom validator
.with_single_cert(certs, keys.remove(0))
.unwrap();
// Configure client certificate verification
server_config.client_auth_root_subjects = {
use rustls::client_cert_verifier::WebPkiClientVerifier;
// Build a verifier with a root store
let roots = rustls::RootCertStore::from_iter(vec![rustls::Certificate(std::fs::read("ca-cert.pem").unwrap())]);
Arc::new(WebPkiClientVerifier::new(roots).unwrap())
};
// Custom key generator that uses client certificate subject
struct CertKeyGenerator;
tower_http::limit::KeyGenerator for CertKeyGenerator {
fn key(&self, req: &Request) -> String {
// Extract client cert from request extensions (populated by axum-rustls or similar)
if let Some(certs) = req.extensions().get::>() {
if let Some(first) = certs.first() {
// Use the subject DN as a stable key; in production you may map this to a user ID
format!("cert:{}", hex::encode(first.0.iter().collect::>()))
} else {
"unknown".to_string()
}
} else {
"no-cert".to_string()
}
}
}
let app = Router::new()
.route("/api", get(handler))
.layer(Extension(server_config)) // share server config if needed
.layer(RateLimitLayer::new(100, std::time::Duration::from_secs(60), Arc::new(CertKeyGenerator)))
.layer(TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
In this example, the key generator derives the rate-limit key from the client certificate subject, ensuring that each authenticated principal is limited independently. To further harden the setup, map certificate fields to internal user IDs to avoid exposing raw subject DNs in logs and to handle certificate rotation gracefully. Additionally, apply per-route limits consistently and validate that the rate limiter is invoked before any business logic to prevent resource exhaustion or timing-based bypasses.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |