Log Injection in Axum with Basic Auth
Log Injection in Axum with Basic Auth — how this specific combination creates or exposes the vulnerability
Log injection occurs when untrusted input is written directly into log entries without sanitization, enabling log forging or log poisoning. In Axum applications that use HTTP Basic Authentication, combining credential handling with logging creates a concrete risk. When a handler parses the Authorization header, extracts the username and password, and then logs values such as username or decision outcomes like "authentication failed" without validation, an attacker can inject newline characters or structured tokens into the log stream.
For example, an attacker could send an Authorization header containing a username like admin\nAuthorization: Basic [credentials]. If the application logs the username verbatim, the forged line can appear as a separate, authoritative log entry, complicating audit analysis. In Axum, this typically arises in two places: (1) during filter execution where authentication state is logged, and (2) in custom logging layers or middleware that record request identifiers alongside credentials-derived context. Because Basic Auth credentials are often base64-encoded in transit but decoded server-side before any logging, the decoded username becomes a direct injection vector if not constrained.
The risk is compounded when logs are aggregated into monitoring or SIEM systems, where newline injection can break log parsers, cause misattribution of events, or facilitate injection into log-based alerting logic. Although Axum itself does not add extra formatting around logs, application code that uses macros like info! or error! with user-controlled strings is susceptible. An attacker may not directly alter application behavior via log injection in Axum, but they can obscure real events, trigger alert fatigue, or provide pivot data for correlated attacks. This specific combination—Basic Auth for access control plus unchecked logging—exemplifies how authentication context can unintentionally amplify injection surfaces.
Basic Auth-Specific Remediation in Axum — concrete code fixes
Remediation focuses on sanitizing any data derived from the Authorization header before it reaches logging statements. Do not log raw usernames or passwords; instead, log normalized, constrained identifiers or hashes. Validate the username against an allowlist or regex before using it in log messages, and ensure that any string interpolation within logging macros is explicitly escaped or omitted.
Below are two Axum handler patterns: one vulnerable and one remediated.
// Vulnerable example: logging raw username from Basic Auth
use axum::{routing::get, Router, extract::Request};
use std::convert::Infallible;
async fn handler(
request: Request,
) -> Result {
if let Some(auth_header) = request.headers().get("authorization") {
if let Ok(auth_str) = auth_header.to_str() {
if auth_str.starts_with("Basic ") {
let encoded = auth_str.strip_prefix("Basic ").unwrap_or("");
// naive decoding for illustration only
if let Ok(decoded) = base64::decode(encoded) {
if let Ok(credentials) = String::from_utf8(decoded) {
let parts: Vec<&str> = credentials.splitn(2, ':').collect();
if parts.len() == 2 {
let (username, _password) = (parts[0], parts[1]);
// Vulnerable: logging raw user input
tracing::info!(username = username, "auth attempt");
}
}
}
}
}
}
Ok((http::StatusCode::OK, "Hello"))
}
The vulnerable example logs the raw username, which may contain newlines or structured text. An attacker can craft a header to inject additional log lines or metadata.
// Remediated example: sanitize before logging
use axum::{routing::get, Router, extract::Request};
use std::convert::Infallible;
async fn handler(
request: Request,
) -> Result {
if let Some(auth_header) = request.headers().get("authorization") {
if let Ok(auth_str) = auth_header.to_str() {
if auth_str.starts_with("Basic ") {
let encoded = auth_str.strip_prefix("Basic ").unwrap_or("");
if let Ok(decoded) = base64::decode(encoded) {
if let Ok(credentials) = String::from_utf8(decoded) {
let parts: Vec<&str> = credentials.splitn(2, ':').collect();
if parts.len() == 2 {
let (username, _password) = (parts[0], parts[1]);
// Remediation: validate and sanitize username
let safe_username = if username.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
username.to_string()
} else {
"unknown".to_string()
};
// Safe: only sanitized identifier logged
tracing::info!(username = safe_username.as_str(), "auth attempt");
}
}
}
}
}
}
Ok((http::StatusCode::OK, "Hello"))
}
In the remediated version, the username is validated against a strict pattern before logging. Newline characters and special symbols are excluded, preventing injection. For production use, consider hashing the username (e.g., with a stable, non-reversible hash) for correlation without exposing raw identifiers. Additionally, structured logging formats can reduce risk by ensuring log lines are parsed as single records. Combine these practices with Axum middleware that enforces authentication uniformly, and avoid logging the password component entirely.