Api Rate Abuse in Axum with Mutual Tls
Api Rate Abuse in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
Rate abuse in Axum with mutual TLS (mTLS) involves a client that presents a valid certificate but still exceeds intended request rates. mTLS ensures the client is who they claim to be by validating a trusted client certificate, but it does not provide built-in request counting or throttling. Because mTLS terminates identity verification at the edge or within the framework layer, developers may assume that authenticated traffic is also rate-limited, which is not automatic. Axum routes are invoked only after the TLS handshake completes and the certificate is verified, so an attacker with a legitimate certificate can open many connections and consume server compute, memory, and connection slots.
The vulnerability becomes pronounced in scenarios where Axum services are deployed behind load balancers or proxies that terminate mTLS. The proxy may forward the request to Axum with a header indicating client identity (e.g., a certificate serial or subject), but Axum itself has no mechanism to associate that identity with a per-client counter. Without an explicit rate-limiting layer keyed to the mTLS identity, an unauthenticated attack surface remains: connection exhaustion, request flooding, or repeated expensive operations such as costly computation or database queries. Even when mTLS blocks unknown clients, known clients can still abuse allowed paths if rate controls are absent.
Real-world patterns mirror findings from OWASP API Top 10 and common SSRF/DoS techniques. For example, an attacker might use a valid mTLS certificate to repeatedly call a /convert endpoint that triggers large file processing, or hammer a search endpoint that performs regex-heavy parsing. Because middleBrick tests unauthenticated attack surfaces and checks rate limiting in parallel with other controls, it can surface cases where mTLS authentication exists but rate limiting does not, producing findings tied to the Rate Limiting category with severity and remediation guidance.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
To mitigate rate abuse while keeping mTLS in Axum, enforce per-identity rate limits after certificate validation. Use the certificate’s serial number or subject as a stable key. Below are concrete, working examples that integrate middleware into an Axum service.
Example 1: mTLS verification with Axum and tower-http
use axum::{
routing::get,
Router,
extract::State,
};
use std::net::SocketAddr;
use tokio::net::TcpListener;
use tower_http::set_header::SetResponseHeaderLayer;
use tower_http::trace::TraceLayer;
use std::sync::Arc;
use tokio::sync::Mutex;
use std::collections::HashMap;
// Shared state for per-client request counts
struct AppState {
counts: Mutex>,
limit: u32,
window_secs: u64,
}
#[tokio::main]
async fn main() {
let state = Arc::new(AppState {
counts: Mutex::new(HashMap::new()),
limit: 100,
window_secs: 60,
});
let app = Router::new()
.route("/hello", get(hello))
.with_state(state)
.layer(SetResponseHeaderLayer::overriding(
tower_http::headers::HeaderName::from_static("x-rate-remaining"),
));
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn hello(
State(state): State>,
// In a real mTLS setup, extract the peer certificate via a tower or axum extension
// Here we simulate identity as a header for clarity
headers: axum::http::HeaderMap,
) -> String {
let identity = headers.get("x-client-cert-serial")
.map(|v| v.to_str().unwrap_or("unknown").to_string())
.unwrap_or_else(|| "unknown".to_string());
let mut counts = state.counts.lock().await;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
// Simple windowed counter reset (in production use a more robust crate like `governor`)
// For brevity, this example uses a naive approach.
let count = counts.entry(identity.clone()).or_insert(0);
*count += 1;
if *count > state.limit {
return "Rate limit exceeded".to_string();
}
format!("Hello, {}", identity)
}
Example 2: Using the `governor` crate for robust rate limiting with mTLS identities
use axum::{
routing::get,
Router,
extract::Extension,
};
use governor::{Quota, RateLimiter};
use governor::key::Key;
use std::time::Duration;
use std::sync::Arc;
use tower_http::trace::TraceLayer;
// A key builder that uses mTLS identity
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct CertKey(String);
impl Key for CertKey {
fn key(&self) -> String {
self.0.clone()
}
}
#[tokio::main]
async fn main() {
// Allow 100 requests per 60 seconds per identity
let quota = Quota::per_minute(100);
let limiter = RateLimiter::new(quota);
let app = Router::new()
.route("/secure", get(secure_endpoint))
.layer(Extension(limiter));
// In production, integrate mTLS via a tower layer that extracts the client certificate
// and builds CertKey from its serial or subject.
// axum::Server::bind(...).serve(app.into_make_service()).await.unwrap();
}
async fn secure_endpoint(
Extension(limiter): Extension>>,n headers: axum::http::HeaderMap,
) -> String {
let identity = headers.get("x-client-cert-serial")
.map(|v| v.to_str().unwrap_or("fallback").to_string())
.unwrap_or_else(|| "fallback".to_string());
let key = CertKey(identity);
if limiter.check(&key).is_err() {
return "Rate limit exceeded".to_string();
}
"Access granted".to_string()
}
These examples show how to couple mTLS identity extraction with per-client rate limiting. For production, prefer a robust crate like governor or ratelimit_meter to handle sliding windows and distributed scenarios. If you use a reverse proxy that handles mTLS, propagate the client certificate identity via headers and apply the same rate-limiting logic in Axum.