HIGH cache poisoningaxum

Cache Poisoning in Axum

How Cache Poisoning Manifests in Axum

Cache poisoning in Axum applications typically occurs when user-controlled input influences cache keys or cache behavior without proper validation. The most common attack vector involves HTTP headers like Accept, Accept-Language, or custom headers that Axum's caching middleware uses to generate cache keys. An attacker can craft requests with manipulated headers to create cache entries that serve poisoned content to other users.

use axum::extract::TypedHeader;
use axum::http::header::ACCEPT;
use axum_extra::cache::http_cache::HttpCache;

async fn endpoint(
    TypedHeader(accept): TypedHeader<ACCEPT>,
    cache: HttpCache,
) -> impl IntoResponse {
    // Vulnerable: accept header directly influences cache key
    let response = cache.around(accept.to_string(), async {
        // Generate response based on accept header
        // Attacker can manipulate accept to poison cache
        Ok(("text/html; charset=utf-8", "malicious content"))
    }).await;
    
    response
}

Another manifestation occurs with query parameter-based caching. Axum applications often cache responses based on query strings, but without proper normalization, different parameter orders or URL-encoded variations can create multiple cache entries for the same logical resource:

async fn search(
    Query(params): Query<HashMap<String, String>>,
    cache: HttpCache,
) -> impl IntoResponse {
    // Vulnerable: different parameter orders create different cache keys
    let cache_key = format!("/search?{:?}", params);
    cache.around(cache_key, async {
        // Search logic here
        Ok(find_users(params.get("name")))
    }).await
}

Cache poisoning can also occur through improper Vary header handling. If an Axum application varies cache entries on user-specific headers like authentication tokens or session IDs without proper isolation, an attacker might cause cache pollution that affects other users' responses:

async fn user_data(
    TypedHeader(auth): TypedHeader<Authorization>,
    cache: HttpCache,
) -> impl IntoResponse {
    // Vulnerable: caching user-specific data without proper isolation
    let cache_key = format!("/user/{}?auth={}", user_id, auth);
    cache.around(cache_key, async {
        // Return user-specific data
        Ok(get_user_data(user_id))
    }).await
}

Axum-Specific Detection

Detecting cache poisoning in Axum applications requires examining both the application code and runtime behavior. Start by auditing your middleware stack for caching components and analyzing how cache keys are generated. Look for patterns where user input directly influences cache keys without normalization or validation.

middleBrick's scanning approach specifically targets these Axum patterns by analyzing the runtime behavior of your API endpoints. The scanner tests for cache poisoning by sending requests with manipulated headers and parameters, then verifying if the responses vary based on those inputs in ways that could indicate vulnerability.

# Scan your Axum API with middleBrick
middlebrick scan https://api.example.com --output json

# Example output showing cache-related findings:
{
  "risk_score": 65,
  "category_breakdown": {
    "Cache Poisoning": {
      "severity": "high",
      "description": "Endpoint /search varies cache on unvalidated query parameters",
      "remediation": "Normalize query parameters before caching"
    }
  }
}

Manual detection involves testing your Axum endpoints with variations in HTTP headers and query parameters. For instance, send the same request with different Accept-Language headers and verify if you receive different cached responses. Use tools like curl or Postman to systematically test:

# Test cache poisoning via Accept header
curl -H "Accept: application/json" https://api.example.com/data
curl -H "Accept: application/xml" https://api.example.com/data

# Test via query parameter variations
curl "https://api.example.com/search?name=john&sort=asc"
curl "https://api.example.com/search?sort=asc&name=john"

Monitor your cache store (Redis, memory cache, etc.) for anomalous entries. Look for cache keys that contain user-controlled input or show patterns of manipulation. Implement logging around cache operations to track which inputs generate which cache keys.

Axum-Specific Remediation

Remediating cache poisoning in Axum requires a multi-layered approach. First, normalize and validate all inputs that influence cache keys. For query parameters, sort them alphabetically and use consistent encoding:

use axum::extract::Query;
use axum_extra::cache::http_cache::HttpCache;
use std::collections::HashMap;

async fn search(
    Query(params): Query<HashMap<String, String>>,
    cache: HttpCache,
) -> impl IntoResponse {
    // Normalize parameters before caching
    let mut sorted_params: Vec<(&String, &String)> = params.iter().collect();
    sorted_params.sort_by_key(|&(k, _)| k.clone());
    
    let cache_key = format!("/search?{:?}", sorted_params);
    
    cache.around(cache_key, async {
        // Search logic here
        Ok(find_users(params.get("name")))
    }).await
}

For header-based caching, validate and whitelist acceptable values before using them in cache keys. Implement strict validation for content negotiation headers:

use axum::extract::TypedHeader;
use axum::http::header::{ACCEPT, HeaderMap};
use axum_extra::cache::http_cache::HttpCache;

async fn endpoint(
    TypedHeader(accept): TypedHeader<ACCEPT>,
    cache: HttpCache,
) -> impl IntoResponse {
    // Validate accept header before using in cache key
    let content_type = match accept.as_str() {
        "application/json" => "application/json",
        "text/html" => "text/html",
        _ => "application/json", // default
    };
    
    let cache_key = format!("/endpoint?ct={}", content_type);
    
    cache.around(cache_key, async {
        // Generate response based on validated content type
        Ok((content_type, generate_content(content_type)))
    }).await
}

Implement cache isolation for user-specific data. Never cache responses that contain sensitive user information without proper isolation mechanisms:

async fn user_data(
    user_id: u32,
    cache: HttpCache,
) -> impl IntoResponse {
    // Cache user data with proper isolation
    let cache_key = format!("/user/{}", user_id);
    
    cache.around(cache_key, async {
        // Only return data for authenticated user
        if !is_authorized(user_id) {
            return (StatusCode::FORBIDDEN, "Forbidden");
        }
        Ok(get_user_data(user_id))
    }).await
}

Consider using cache key prefixes or namespaces to prevent cross-contamination between different API versions or environments. Implement cache entry TTLs that are appropriate for your data's sensitivity and volatility.

Frequently Asked Questions

How can I test if my Axum application is vulnerable to cache poisoning?
Test by sending requests with manipulated headers and query parameters, then verify if the responses vary based on those inputs. Use middleBrick's automated scanning to identify cache poisoning vulnerabilities, or manually test with tools like curl by varying Accept headers, query parameter order, and URL encoding. Monitor your cache store for anomalous entries that contain user-controlled input.
Does Axum have built-in protection against cache poisoning?
Axum itself doesn't provide specific cache poisoning protection, but it offers tools through axum_extra's caching middleware that you can use securely. The HttpCache middleware allows you to control how cache keys are generated. You need to implement proper input validation, parameter normalization, and cache isolation yourself. middleBrick can help identify vulnerabilities in your caching implementation.