Missing Tls in Axum with Mutual Tls
Missing Tls in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
When an Axum service is configured to require Mutual TLS (mTLS) but the server-side TLS settings do not enforce client certificate verification, the endpoint effectively operates without the intended transport-layer authentication. This misconfiguration creates a Missing TLS vulnerability in an otherwise mTLS-protected design: clients may connect successfully, but the server cannot verify their identity. Attackers can capture or spoof requests that should have been authenticated using client certificates, leading to unauthorized access, BOLA/IDOR, or data exposure.
Mutual TLS relies on both parties proving their identity. If the Axum server does not validate presented client certificates (for example, by not setting verify_peer and verify_peer_name in the Rust/TLS stack or equivalent settings in your application layer), the channel is not mutually authenticated. An attacker who can reach the network path can send unauthenticated requests that the server accepts because it only checks that a TLS connection exists, not that it is backed by a trusted client certificate. This bypasses the intended access control boundary introduced by mTLS and can expose sensitive endpoints that assume strong client authentication.
Additionally, if the server trusts any CA rather than a pinned client CA, it may accept certificates issued by unrelated or compromised CAs. This expands the attack surface and can allow lateral movement if an attacker obtains any valid certificate from a broadly trusted CA. Insecure protocol choices (such as allowing older TLS versions or weak cipher suites) further weaken the protection that mTLS is meant to provide, making it easier to downgrade or intercept traffic. The combination of an mTLS-intended API with missing or lax server-side verification turns a security control into a false sense of protection.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
To properly enforce Mutual TLS in Axum, you must configure the underlying HTTPS layer to require and validate client certificates. Below are concrete Rust examples using hyper with rustls, which is a common stack for Axum services. These examples ensure that the server requests and validates client certificates against a trusted CA and that the certificate contents are inspected for authorization decisions.
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 std::sync::Arc;
async fn make_mtls_router() -> Router {
// Load server certificate and private key
let cert_file = &mut BufReader::new(File::open("server.crt").unwrap());
let key_file = &mut BufReader::new(File::open("server.key").unwrap());
let cert_chain: Vec<Certificate> = certs(cert_file).unwrap().into_iter().map(Certificate).collect();
let mut keys: Vec<PrivateKey> = pkcs8_private_keys(key_file).unwrap().into_iter().map(PrivateKey).collect();
// Configure client certificate verification
let mut server_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth() // Start with no auth; we will require it
.with_single_cert(cert_chain, keys.remove(0))
.expect("invalid certificate or key");
// Require and verify client certificates against a trusted CA
let client_ca = rustls::CertificateAuthorityTrustAnchor::from_web_pki_roots();
let mut client_verifier = rustls::client::WebPkiServerVerifier::new_with_single_cert(
vec![rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
// Replace with your CA's DER-encoded SubjectPublicKeyInfo
vec![/* DER bytes */],
vec![],
vec![],
)],
);
server_config.client_auth_mandatory = true;
server_config.client_auth_root_subjects = client_ca.into();
server_config.verify_scts = true;
let config = Arc::new(server_config);
let listener = tokio_rustls::TlsAcceptor::from(config);
// Build Axum router as usual
let app = Router::new();
// In practice, you would bind with hyper + tls acceptor to enforce mTLS on each connection
app
}
// In your main, bind with TLS:
// let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
// let tls_acceptor = listener;
// axum::serve(tls_acceptor, app).await.unwrap();
The key elements are:
- Set
client_auth_mandatory = trueto require client certificates. - Provide a trusted set of client CA subject identifiers so the server rejects certificates not signed by those CAs.
- Optionally inspect the client certificate’s fields (e.g., SANs or OIDs) in your route handlers for fine-grained authorization, but the transport enforcement happens at the TLS layer.
If you use a framework that abstracts TLS (e.g., via a reverse proxy or gateway), ensure the proxy is configured to request and validate client certificates and that Axum trusts only proxied requests that have been properly authenticated. Do not rely on headers like X-SSL-Cert without strict edge configuration, as they can be spoofed.
Related CWEs: encryption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-319 | Cleartext Transmission of Sensitive Information | HIGH |
| CWE-295 | Improper Certificate Validation | HIGH |
| CWE-326 | Inadequate Encryption Strength | HIGH |
| CWE-327 | Use of a Broken or Risky Cryptographic Algorithm | HIGH |
| CWE-328 | Use of Weak Hash | HIGH |
| CWE-330 | Use of Insufficiently Random Values | HIGH |
| CWE-338 | Use of Cryptographically Weak PRNG | MEDIUM |
| CWE-693 | Protection Mechanism Failure | MEDIUM |
| CWE-757 | Selection of Less-Secure Algorithm During Negotiation | HIGH |
| CWE-261 | Weak Encoding for Password | HIGH |