Brute Force Attack in Axum with Basic Auth
Brute Force Attack in Axum with Basic Auth — how this specific combination creates or exposes the vulnerability
Axum is a Rust web framework that does not provide built-in authentication, so developers commonly implement HTTP Basic Auth by manually inspecting the Authorization header. When Basic Auth is used without additional protections, it becomes highly susceptible to brute force attacks. In a brute force scenario, an attacker systematically iterates over many username and password combinations, submitting each pair as an Authorization: Basic base64(username:password) header. Because the protocol itself sends credentials in every request (albeit base64-encoded, not encrypted), an attacker who can observe or intercept traffic can validate guesses quickly. MiddleBrick’s authentication checks flag scenarios where endpoints accept repeated failed Basic Auth attempts without introducing delays, lockouts, or other rate-limiting mechanisms. Without server-side throttling or progressive backoff, each request is evaluated independently, allowing rapid iteration over credential pairs. Axum applications that rely solely on Basic Auth over TLS but do not enforce per-user rate limits or account lockout policies expose a large attack surface. The scanner’s authentication checks correlate repeated 401 responses with different credentials and missing server-side protections, identifying the endpoint as vulnerable to credential-guessing attacks. Even when TLS is used to protect transport, weak passwords and reused credentials make Basic Auth particularly dangerous in high-risk environments.
Basic Auth-Specific Remediation in Axum — concrete code fixes
To mitigate brute force risks in Axum when using Basic Auth, combine server-side rate limiting, per-account attempt tracking, and secure credential storage. Axum does not include authentication primitives, so protections must be implemented explicitly via middleware or extractor logic. Below are concrete, working examples that integrate rate limiting and secure credential verification.
Example 1: Basic Auth extractor with constant-time comparison and rate limiting by username
use axum::{
async_trait,
extract::{FromRequest, Request},
http::StatusCode,
};
use std::{
collections::HashMap,
convert::Infallible,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
use ring::constant_time::verify;
// Simple in-memory rate limiter: track attempts per username
#[derive(Clone)]
struct RateLimiter {
attempts: Arc>>>,
max_attempts: usize,
window: Duration,
}
impl RateLimiter {
fn new(max_attempts: usize, window: Duration) -> Self {
Self {
attempts: Arc::new(Mutex::new(HashMap::new())),
max_attempts,
window,
}
}
fn record(&self, key: &str) -> bool {
let mut attempts = self.attempts.lock().unwrap();
let now = Instant::now();
let entries = attempts.entry(key.to_string()).or_default();
entries.retain(|t| now.duration_since(*t) < self.window);
if entries.len() >= self.max_attempts {
return false;
}
entries.push(now);
true
}
}
// A secure Basic Auth extractor
struct BasicAuth {
username: String,
password_hash: Vec<u8>, // stored hash, not plaintext
}
#[async_trait]
impl FromRequest<S> for BasicAuth
where
S: Send + Sync,
{
type Rejection = (StatusCode, &'static str);
async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
let header = req.headers().get("authorization")
.ok_or((StatusCode::UNAUTHORIZED, "missing authorization header"))?;
let header_str = header.to_str().map_err(|_| (StatusCode::UNAUTHORIZED, "invalid header"))?;
if !header_str.starts_with("Basic ") {
return Err((StatusCode::UNAUTHORIZED, "invalid authorization type"));
}
let data = general_purpose::STANDARD.decode(&header_str[6..])
.map_err(|_| (StatusCode::UNAUTHORIZED, "invalid base64"))?;
let parts: Vec<_> = data.splitn(2, |&b| b == b':').collect();
if parts.len() != 2 {
return Err((StatusCode::UNAUTHORIZED, "invalid credentials"));
}
let (username, candidate) = (parts[0], parts[1]);
// In practice, fetch the stored hash for `username` from a secure store
let stored_hash = fetch_stored_hash_for_testing(username);
// Use constant-time verification to avoid timing attacks
let ok = verify(&stored_hash, candidate).is_ok();
if !ok {
return Err((StatusCode::UNAUTHORIZED, "invalid credentials"));
}
Ok(Self {
username: String::from_utf8_lossy(username).to_string(),
password_hash: stored_hash.to_vec(),
})
}
}
// Example handler using the extractor and rate limiter
async fn login(
auth: BasicAuth,
limiter: <RateLimiter>,
) -> Result<impl IntoResponse, (StatusCode, &'static str)> {
if !limiter.record(&auth.username) {
return Err((StatusCode::TOO_MANY_REQUESTS, "rate limit exceeded"));
}
Ok(format!("Welcome {}", auth.username))
}
// Dummy storage for example purposes
fn fetch_stored_hash_for_testing(username: &[u8]) -> Vec<u8> {
// In production, retrieve a strong hash (e.g., Argon2id or bcrypt) for the user
// This example returns a static hash for "alice" with password "correcthorse"
if username == b"alice" {
// This is a test hash placeholder; use proper password hashing in real code
return "$argon2id$v=19$m=65536,t=3,p=1$..."
.as_bytes()
.to_vec();
}
// Return a hash that will never verify, simulating unknown user
vec![0; 32]
}
Example 2: Global rate limiting middleware in Axum
use axum::{middleware::Next, response::IntoResponse, Json, Router};
use std::net::SocketAddr;
use tower_http::limit::RateLimitLayer;
use tower_http::set_header::SetResponseHeaderLayer;
use tower_http::trace::TraceLayer;
// Apply a global rate limit to all routes
fn app() -> Router {
Router::new()
.route("/protected", axum::routing::get(|| async { "ok" }))
.layer(RateLimitLayer::new(5, std::time::Duration::from_secs(60))) // 5 requests per 60s per IP
.layer(SetResponseHeaderLayer::overriding(
tower_http::headers::HeaderName::from_static("x-rate-limit-remaining"),
))
}
#[tokio::main]
async fn main() {
let app = app();
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
These examples show how to combine a custom Basic Auth extractor with constant-time verification and rate limiting to reduce brute force risk. For stronger security, prefer session-based or token-based authentication (e.g., JWT with refresh rotation) over Basic Auth where possible. MiddleBrick’s authentication checks can validate that endpoints include server-side rate limiting and do not leak information via timing differences or repeated 401 responses without mitigation.