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.