HIGH axumapi key enumeration

Api Key Enumeration in Axum

How API Key Enumeration Manifests in Axum

API key enumeration is an attack where an adversary systematically discovers valid API keys by analyzing application responses. In Axum applications, this vulnerability frequently arises from two distinct code patterns: predictable key generation and inconsistent authentication error handling.

Predictable Key Generation: Axum developers sometimes generate API keys using insufficient entropy, such as sequential integers, timestamps, or user-controllable values. A common anti-pattern is embedding a user ID or email directly into the key. For example:

use axum::{extract::Path, routing::post, Router};
use uuid::Uuid;

async fn create_api_key(Path(user_id): Path<u64>) -> String {
    // VULNERABLE: Predictable key based on user_id
    format!("key-{}", user_id)
}

Here, an attacker can simply increment user_id values to enumerate keys (key-1001, key-1002, etc.). Even using Uuid::new_v4() is safe, but storing or exposing it in URLs (e.g., /api/keys/{key}) without access control turns any predictable or guessable identifier into an enumeration vector.

Inconsistent Error Messages: Axum's flexible error handling can inadvertently leak information. Consider a handler that retrieves a key from a database:

use axum::{
    extract::State, http::StatusCode, response::IntoResponse,
    routing::get, Router
};
use std::collections::HashMap;

async fn get_key(
    State(db): State<HashMap<String, String>>,
    axum::extract::Header(api_key): axum::extract::Header<String>
) -> impl IntoResponse {
    match db.get(&api_key) {
        Some(key) => (StatusCode::OK, key.clone()),
        None => (StatusCode::NOT_FOUND, "Invalid API key".to_string()),
    }
}

While this returns NOT_FOUND for invalid keys, a different handler might return UNAUTHORIZED or a different message for the same condition. An attacker can probe with candidate keys and map response patterns (404 vs 401, body length differences, timing discrepancies) to distinguish valid from invalid keys. Axum's layer system can compound this if different routes apply different authentication middleware with divergent error responses.

Axum-Specific Detection

Detecting API key enumeration in Axum requires analyzing both the application's runtime behavior and its OpenAPI specification. middleBrick's scanner performs black-box tests against the live endpoint, specifically looking for Axum-relevant patterns.

Runtime Behavioral Analysis: The scanner submits a sequence of candidate API keys (e.g., key-1, key-2, key-test) to authenticated endpoints. It measures HTTP status codes, response body consistency, and timing differences. A significant variance (e.g., 200 OK for one key, 404 for another on the same route) indicates a potential enumeration flaw. For Axum apps using axum::extract::Header for keys, this is a primary test vector.

OpenAPI/Swagger Spec Analysis: If the Axum service provides an OpenAPI spec (often via utoipa or axum-openapi), middleBrick resolves all $ref definitions to find security scheme declarations. It checks for:

  • Missing apiKey scheme: If the spec defines no API key security scheme, but runtime endpoints require one, this mismatch suggests key validation might be inconsistently applied.
  • Predictable parameter names: Specs that expose key parameters in query strings or path segments (e.g., /keys/{api_key}) without security requirements on those operations increase enumeration risk.

Scanning with middleBrick: Use the CLI to test your Axum service:

middlebrick scan https://api.youraxumapp.com

The scan runs 12 parallel checks, including Authentication and BOLA/IDOR categories, which directly cover key enumeration. The report will flag any endpoint where key validation responses are inconsistent or where keys appear in URLs. For CI/CD integration, add the GitHub Action to your workflow to fail builds if the security score drops due to an enumeration finding.

Axum-Specific Remediation

Remediation in Axum centers on enforcing uniform, non-enumerable authentication and eliminating key exposure in URLs. Use Axum's extractors and middleware to create a consistent security layer.

1. Use a Uniform Authentication Layer: Apply a single middleware that handles all API key validation and returns the same error response for any failure. This prevents attackers from using response differences to enumerate keys.

use axum::{
    async_trait, body::Body, extract::Request, http::StatusCode,
    middleware::Next, response::Response
};

struct ApiKeyAuth;

#[async_trait]
implement<axum::middleware::FromRequestParts<()> for ApiKeyAuth {
    async fn from_request_parts<S: Send + Sync>(
        parts: &mut axum::http::request::Parts,
        _state: &S,
    ) -> Result<Self, (StatusCode, String)> {
        let key = parts
            .headers
            .get("X-API-Key")
            .ok_or((StatusCode::UNAUTHORIZED, "Missing API key".to_string()))?
            .to_str()
            .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid API key header".to_string()))?;

        // Validate key against database (use constant-time compare if possible)
        if !is_valid_key(key).await {
            return Err((StatusCode::UNAUTHORIZED, "Invalid API key".to_string()));
        }

        Ok(ApiKeyAuth)
    }
}

async fn is_valid_key(key: &str) -> bool {
    // Your database lookup here. Use constant-time comparison for secrets.
    // Example: use argon2/ring for secure storage and verification.
    false // placeholder
}

// Apply globally
let app = Router::new()
    .route("/data", get(protected_handler))
    .layer(axum::middleware::from_fn(|req: Request, next: Next| async move {
        // Manually invoke the extractor logic for all routes
        match ApiKeyAuth::from_request_parts(&mut req.into_parts().0, &())
            .await
        {
            Ok(_) => Ok(next.run(req).await),
            Err((status, body)) => Ok((status, body).into_response()),
        }
    }));

2. Never Expose Keys in URLs: Axum routes should never capture API keys as path parameters (/keys/:key). Use headers (X-API-Key) or, for browser clients, secure, HttpOnly cookies. If you must use query parameters (discouraged), ensure the route is protected by the same uniform auth layer and never logs the full URL.

3. Generate Unpredictable Keys: Use cryptographically secure random generators. The uuid crate's Uuid::new_v4() is acceptable, but for shorter keys consider rand::Rng with ThreadRng and a sufficiently large alphabet (e.g., 32-character base64). Store only hashed keys (e.g., with argon2) in your database, similar to passwords.

use rand::{Rng, distributions::Alphanumeric};

fn generate_secure_api_key(length: usize) -> String {
    rand::thread_rng()
        .sample_iter(&Alphanumeric)
        .take(length)
        .map(char::from)
        .collect()
}

// Store hash in DB
use argon2::Argon2;
use password_hash::{PasswordHash, PasswordHasher, SaltString};

fn hash_api_key(key: &str) -> String {
    let salt = SaltString::generate(&mut rand::thread_rng());
    let argon2 = Argon2::default();
    let hash = argon2
        .hash_password(key.as_bytes(), &salt)
        .unwrap()
        .to_string();
    hash
}

4. Validate Consistently Across Environments: Ensure your development, staging, and production Axum apps use the same authentication middleware configuration. Use the middleBrick GitHub Action to scan staging APIs before deployment, catching configuration drift that might introduce enumeration flaws.

Frequently Asked Questions

Can middleBrick detect if my Axum app's API keys are predictable?
Yes. middleBrick's black-box scanner tests for enumeration by submitting a sequence of candidate keys and analyzing response patterns (status codes, body content, timing). It also analyzes your OpenAPI spec for exposed key parameters. The report will flag inconsistent authentication responses as a BOLA/IDOR or Authentication finding, which covers predictable key issues.
How do I fix API key enumeration in my Axum application's CI/CD pipeline?
First, implement a uniform authentication middleware as shown above. Then, integrate the middleBrick GitHub Action into your workflow. Configure it to scan your staging API on every pull request. Set a security score threshold (e.g., 'B') in the action's min-score parameter. If the scan detects an enumeration flaw (or any other issue) that drops the score below the threshold, the build will fail, preventing deployment.