MEDIUM format stringaxumbasic auth

Format String in Axum with Basic Auth

Format String in Axum with Basic Auth — how this combination creates or exposes the vulnerability

A format string vulnerability occurs when user-controlled input is passed directly into a formatting function such as format! or println! without explicit format specification. In Axum, this risk can emerge in authentication logic that processes Basic Auth credentials. Basic Auth sends credentials as a base64-encoded string in the request header Authorization: Basic {base64(credentials)}. If a developer decodes this value and then uses it to construct log messages, error responses, or debug output with unchecked format strings, an attacker can supply format specifiers like %s, %x, or %n to read stack memory or cause crashes.

Consider an Axum handler that decodes the Basic Auth header and logs the username using a format string built from user input:

// Vulnerable example: format string risk with Basic Auth in Axum
use axum::{
    async_trait,
    extract::Request,
    handler::Handler,
    middleware::Next,
    response::IntoResponse,
};
use std::convert::Infallible;

async fn auth_middleware(req: Request, next: Next<B>) -> impl IntoResponse {
    if let Some(auth_header) = req.headers().get("authorization") {
        if let Ok(auth_str) = auth_header.to_str() {
            if auth_str.starts_with("Basic ") {
                let encoded = auth_str.trim_start_matches("Basic ");
                // Decode base64 (simplified for example)
                if let Ok(decoded) = base64::decode(encoded) {
                    if let Ok(credentials) = String::from_utf8(decoded) {
                        let parts: Vec<&str> = credentials.split(':').collect();
                        if parts.len() == 2 {
                            let username = parts[0];
                            // Risk: using user-controlled username in format string
                            println!(format!("Authenticated user: {}", username));
                        }
                    }
                }
            }
        }
    }
    next.run(req).await
}

In the above, the call to println!(format!(...)) is incorrect; it effectively becomes a format string call where username is interpreted as a format argument. If an attacker sends a username like test%s%s%s, the program may read values from the stack, potentially exposing internal pointers or sensitive data. While the example uses println!, similar issues can arise when building error messages that are returned to the client, especially if the response body includes user-controlled data interpolated via macros or formatting utilities.

Another scenario involves constructing HTTP responses with user data embedded in strings that are later formatted. For example, returning a JSON error message that includes the decoded username without proper escaping can lead to injection-like behavior in downstream parsers if the output is later processed by a vulnerable logging or templating system. In Axum, handlers often return impl IntoResponse, and developers might inadvertently embed credentials in status messages or headers. Because Basic Auth credentials are available in the request, any formatting that does not enforce strict format strings can become a vector for memory disclosure or denial of service.

To summarize, the combination of Format String and Basic Auth in Axum arises when credentials extracted from the Authorization header are used in logging, error responses, or any code path that invokes formatting macros without using explicit format syntax (e.g., format_args! with clear placeholders). The vulnerability is not in Axum itself but in how developers handle and format the extracted data.

Basic Auth-Specific Remediation in Axum — concrete code fixes

Remediation focuses on avoiding direct interpolation of user-supplied values into format strings and ensuring that any constructed output uses safe, explicit formatting. In Axum, this means treating Basic Auth credentials as untrusted data and applying consistent sanitization and structured logging practices.

1. Use explicit formatting and avoid passing user input as a format string:

Instead of println!(format!("Authenticated user: {}", username)), use println!("Authenticated user: {}", username). This ensures that username is treated as a plain argument, not as a format string.

// Safe: explicit formatting with user data as an argument
let username = parts[0];
println!("Authenticated user: {}", username);

2. Prefer structured logging with key-value pairs:

Use a logging facade like tracing to emit structured events. This avoids string formatting entirely for diagnostic output and reduces the risk of accidental format string bugs.

// Safe: structured logging with tracing
use tracing::info;
info!(user = %username, "Authentication attempt");

3. Validate and sanitize credentials before use:

Perform strict validation on the decoded credentials (e.g., length checks, allowed character sets) and avoid using raw credentials in any output that might be formatted. If you must include user data in responses, encode or escape it appropriately for the target context (e.g., JSON serialization via serde_json).

// Safe: validate and serialize safely
if parts.len() == 2 {
    let username = parts[0];
    let password = parts[1];
    // Validate username (e.g., alphanumeric only)
    if username.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
        // Use serde_json to build a safe JSON response
        let error_response = serde_json::json!({
            "error": "invalid_credentials",
            "user": username
        });
        // Return as appropriate Axum response
    }
}

4. Configure Axum middleware carefully:

If implementing middleware for authentication, ensure that any logging or error handling within the middleware follows the same rules. Avoid extracting credentials and then passing them through multiple layers where formatting might occur implicitly.

// Safe: minimal processing, no format string misuse
async fn auth_middleware<B>(req: Request, next: Next<B>) -> impl IntoResponse {
    // Extract and validate without building dynamic format strings
    // ... validation logic ...
    next.run(req).await
}

By treating Basic Auth credentials as opaque data and using explicit formatting or structured logging, developers can prevent format string vulnerabilities in Axum applications while still leveraging Basic Auth as an authentication mechanism.

Frequently Asked Questions

Why is using format!(format!(...)) with Basic Auth credentials risky in Axum?
It can turn the user-controlled credential into a format string argument, enabling memory disclosure or crashes via format specifiers like %s or %x.
What is the safest way to log Basic Auth–related events in an Axum service?
Use structured logging (e.g., tracing::info with key-value fields) and avoid interpolating raw credentials into format strings.