Rainbow Table Attack in Axum with Mutual Tls
Rainbow Table Attack in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
A rainbow table attack targets weak password storage by using precomputed hash chains to reverse cryptographic digests back to plaintext credentials. In Axum, if authentication relies only on a hashed password without a unique salt per user, an attacker who obtains the hash database can use a rainbow table to identify common passwords quickly.
When Mutual TLS (mTLS) is used for client authentication in Axum, the presence of mTLS does not inherently protect the server-side password storage. mTLS ensures that the client possesses a valid certificate and that the TLS channel is mutually authenticated, but it does not change how passwords are stored on the server. If the server stores unsalted or poorly hashed passwords, an attacker who bypasses or compromises the mTLS boundary (for example, by stealing a certificate or exploiting a misconfiguration) can still attempt offline attacks against the password database.
The combination creates a nuanced risk: mTLS reduces the attack surface by ensuring client authenticity, but it may inadvertently encourage a false sense of security. Developers might assume mTLS alone secures authentication and neglect proper password hashing. If an attacker compromises a client certificate or exploits an unauthenticated endpoint that does not enforce mTLS, they could gain access to backend services where weak password hashes exist. Additionally, if session tokens or API keys are derived or stored poorly after mTLS authentication, rainbow tables could be used to crack those stored values if they rely on low-entropy inputs.
In practice, an attacker leveraging a rainbow table against an Axum service with mTLS would first need to obtain the password hash store, perhaps via an insecure endpoint or a misconfigured data exposure check. The attacker then generates or purchases a rainbow table for common hashing algorithms like unsalted MD5 or SHA-1, which Axum might historically have used for compatibility. Because mTLS does not salt hashes or apply key stretching, the attacker can rapidly match hashes to plaintext passwords using the table.
To detect such risks, scans include checks for weak hashing, missing salts, and insufficient key derivation alongside authentication and data exposure checks. These findings highlight whether Axum endpoints inadvertently expose hash values or accept weak credentials, regardless of mTLS usage.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
Remediation focuses on ensuring mTLS is correctly configured and that password storage remains strong even when mTLS is in use. Below are concrete Axum examples that demonstrate proper mTLS setup and secure credential handling.
1. Configure mTLS in Axum using tower-rs/tonic or axum::extract::connect_info::PeerAddr in combination with a TLS acceptor that requests client certificates. Ensure the server rejects connections without a valid client cert.
use axum::routing::get;
use axum::Router;
use std::net::SocketAddr;
use tokio_rustls::rustls::{ServerConfig, NoClientAuth, RootCertStore, Certificate, PrivateKey};
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
use tokio_rustls::TlsAcceptor;
use std::sync::Arc;
async fn build_axum_with_mtls() -> Router {
// Load server certificate and key
let certs = load_certs("server-cert.pem");
let key = load_private_key("server-key.pem");
let mut config = ServerConfig::builder()
.with_no_client_auth() // We will change this to require client auth
.with_single_cert(certs, key)
.expect("invalid server config");
// Require client certificates
let mut client_root_store = RootCertStore::empty();
client_root_store.add(&CertificateDer::from(load_cert("client-ca.pem")));
config.client_auth_root_subjects = client_root_store.into_subjects();
config.client_auth = Some(Arc::new(|_end_entity, _intermediates| Ok(true)));
let tls_acceptor = TlsAcceptor::from(Arc::new(config));
// Bind and serve with TLS, ensuring each connection presents a valid client cert
// Axum routes remain unchanged; TLS enforcement happens at the transport layer
Router::new().route("/secure", get(|| async { "mTLS protected" }))
}
fn load_certs(path: &str) -> Vec> {
// implementation to read PEM files
vec![]
}
fn load_private_key(path: &str) -> PrivateKeyDer<'static> {
// implementation to read private key
PrivateKeyDer::Pkcs8(vec![].into())
}
fn load_cert(path: &str) -> CertificateDer<'static> {
// implementation to read CA cert
CertificateDer::from(vec![])
}
2. Enforce that the authenticated client identity is mapped to application-level permissions. Do not rely solely on the certificate’s subject for authorization; validate roles or permissions within your application logic after mTLS authentication.
use axum::extract::connect_info::PeerAddr;
use axum::http::HeaderValue;
async fn handler(
PeerAddr(addr): PeerAddr>>,
) -> String {
// Extract client certificate details from the TLS stream
// Verify permissions based on certificate fields or mapped identities
"Authenticated via mTLS".to_string()
}
3. Use strong, salted password hashing (e.g., Argon2 or bcrypt) for any user credentials, independent of mTLS. Even with mTLS, stored passwords should be resilient to offline attacks.
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use rand::rngs::OsRng;
fn hash_password(plain: &str) -> String {
let salt = OsRng.gen::<[u8; 16]>();
let argon2 = Argon2::default();
let hash = argon2.hash_password(plain.as_bytes(), &salt).unwrap();
hash.to_string()
}
fn verify_password(plain: &str, stored_hash: &str) -> bool {
let parsed = PasswordHash::new(stored_hash).unwrap();
Argon2::default().verify_password(plain.as_bytes(), &parsed).is_ok()
}
4. Rotate and revoke client certificates regularly and monitor for unauthorized certificate usage. Combine mTLS with short-lived session tokens to limit the impact of a compromised credential.