Rainbow Table Attack in Axum with Basic Auth
Rainbow Table Attack in Axum with Basic Auth — how this specific combination creates or exposes the vulnerability
A rainbow table attack leverages precomputed hashes to reverse cryptographic digests quickly. When Basic Auth is used in an Axum service without additional protections, the server typically performs a direct comparison between a client-supplied password and a stored credential. If the stored credential is a simple hash of the password (e.g., SHA-256 without a unique salt per user), an attacker who obtains the hash database can generate or download a rainbow table covering common passwords and look up matching entries offline. This offline work shifts the cost of password recovery away from real-time rate-limited login attempts and allows an attacker to recover plaintext passwords rapidly once the hash set is acquired.
In the Axum + Basic Auth context, the authentication flow often involves extracting the Authorization header, base64-decoding the "username:password" string, and comparing the password (or its hash) against stored credentials. If the comparison is performed on a fast, unsalted hash and the server does not enforce rate limiting or use a slow, memory-hard key derivation function, an attacker who can observe or infer the hash (for example, through a side channel, log exposure, or a separate data breach) can leverage a rainbow table to identify weak passwords. Even when the service transmits credentials over TLS, poor password storage practices mean that a server-side compromise can lead to immediate credential recovery. This is especially concerning because Basic Auth sends credentials on every request; if the password is weak and the hash is unsalted, a stolen hash database combined with a rainbow table can reveal credentials without needing to intercept traffic.
Compounding the issue, Axum applications that rely on Basic Auth may inadvertently encourage weak passwords if developers do not enforce strong password policies or provide mechanisms like multi-factor authentication. Rainbow tables are most effective against low-entropy secrets; therefore, the combination of predictable user-chosen passwords and fast hash verification creates a practical path for attackers to escalate from a single leaked hash to valid credentials. Mitigations must therefore focus on strengthening password storage and transmission practices within the Axum application rather than relying on transport security alone.
Basic Auth-Specific Remediation in Axum — concrete code fixes
To defend against rainbow table attacks in Axum, store passwords using a dedicated password hashing function with a unique salt and appropriate work factor. Avoid storing raw passwords or using fast, unsalted hashes like plain SHA-256. Use crates such as argon2, bcrypt, or pbkdf2 to derive a verifier from the user-supplied password. When verifying credentials provided via Basic Auth, compare the derived hash against the stored verifier rather than comparing plaintext or weakly hashed values.
Below is a realistic Axum example using argon2 for password hashing and verification with Basic Auth. It includes extracting credentials, verifying the hash, and returning appropriate responses without exposing sensitive information in logs.
use axum::{
async_trait,
extract::{self, FromRequest},
http::{self, Request, StatusCode},
response::IntoResponse,
};
use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2,
};
use serde::Deserialize;
use std::convert::Infallable;
// Simulated user store; in production, use a secure database with per-user salts.
#[derive(Debug, Clone)]
struct User {
username: String,
password_hash: String, // stored argon2 hash
}
#[derive(Debug, Deserialize)]
struct Credentials {
username: String,
password: String,
}
struct BasicAuth {
credentials: Credentials,
}
#[async_trait]
extract::FromRequest for BasicAuth {
type Rejection = (StatusCode, &'static str);
async fn from_request(req: Request<()>) -> Result {
let auth_header = req
.headers()
.get(http::header::AUTHORIZATION)
.ok_or((StatusCode::UNAUTHORIZED, "Missing authorization header"))?;
let auth_str = auth_header
.to_str()
.map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid authorization header"))?
.strip_prefix("Basic ")
.ok_or((StatusCode::UNAUTHORIZED, "Invalid authorization scheme"))?;
let decoded = base64::decode(auth_str)
.map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid base64 encoding"))?;
let cred_str = String::from_utf8(decoded).map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid credentials encoding"))?;
let parts: Vec<&str> = cred_str.splitn(2, ':').collect();
if parts.len() != 2 {
return Err((StatusCode::UNAUTHORIZED, "Invalid credentials format"));
}
let credentials = Credentials {
username: parts[0].to_string(),
password: parts[1].to_string(),
};
Ok(BasicAuth { credentials })
}
}
async fn verify_credentials(creds: &Credentials, user_store: &[User]) -> bool {
// In practice, fetch the user by username from a secure data store.
let user = match user_store.iter().find(|u| u.username == creds.username) {
Some(u) => u,
None => return false, // Avoid timing leaks; use a dummy hash in production.
};
let parsed_hash = match PasswordHash::new(&user.password_hash) {
Ok(h) => h,
Err(_) => return false,
};
let argon2 = Argon2::default();
argon2.verify_password(creds.password.as_bytes(), &parsed_hash).is_ok()
}
async fn login_handler(auth: BasicAuth) -> impl IntoResponse {
// Example user store; replace with a secure database lookup.
let user_store = vec![User {
username: "alice".to_string(),
// This hash would be created at registration with a unique salt.
password_hash: "$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObGkSuGH4E2tOI".to_string(),
}];
if verify_credentials(&auth.credentials, &user_store).await {
(StatusCode::OK, "Authenticated").into_response()
} else {
(StatusCode::UNAUTHORIZED, "Invalid credentials").into_response()
}
}
#[tokio::main]
async fn main() {
let app = axum::Router::new().route("/login", axum::routing::post(login_handler));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
Key remediation points:
- Use a dedicated password hashing function (Argon2id, bcrypt, or PBKDF2) with a unique salt per user.
- Never store or compare raw passwords or fast, unsalted hashes that are vulnerable to rainbow table attacks.
- Ensure the hash comparison is constant-time to prevent timing attacks.
- Enforce strong password policies and consider multi-factor authentication to reduce reliance on password-only protection.
By adopting these practices, an Axum service using Basic Auth can effectively mitigate the risk of offline password recovery via rainbow tables, even if an attacker gains access to the stored credential hashes.