Zone Transfer in Axum
How Zone Transfer Manifests in Axum
In Axum, Zone Transfer describes a race condition vulnerability where per-request data (like user identity) is improperly stored in shared application State, causing data to leak between concurrent requests. Axum's State is designed for immutable, application-wide data (e.g., database pools). When used for request-scoped information, concurrent requests overwrite each other's values, leading to horizontal privilege escalation.
Consider a middleware that authenticates a user and stores their ID in State wrapped in a Mutex:
use axum::{extract::State, http::StatusCode, response::Response, routing::post, Router};
use std::sync::{Arc, Mutex};
struct AppState {
current_user: Mutex<Option<String>>, // Shared state misused for per-request data
}
async fn auth_middleware(
mut req: axum::http::Request<axum::body::Body>,
next: axum::middleware::Next<State<Arc<AppState>>>,
) -> Response {
let user_id = get_user_id_from_request(&req).await; // e.g., from a cookie or header
// UNSAFE: Writing per-request data to shared state
*req.extensions().get::<State<Arc<AppState>>>().unwrap().current_user.lock().unwrap() = Some(user_id);
next.run(req).await
}
async fn handler(State(state): State<Arc<AppState>>) -> Response {
let user_id = state.current_user.lock().unwrap().clone().unwrap();
// Returns data for the user stored in shared state, which may be from another request!
get_user_data(user_id).await
}If two requests interleave, Request B may read the user_id set by Request A, exposing A's data to B. This violates OWASP API Top 10: Broken Authentication and Broken Access Control (API1:2023, API5:2023). The race window is small but exploitable under load, and the vulnerability is silent—no errors occur, only incorrect data returns.
Axum-Specific Detection
Code Review Patterns: Search for State used with interior mutability (Mutex, RwLock, RefCell) where values are set in middleware/handlers and read later. Also flag any State field that holds request-specific types like session IDs, user principals, or request IDs. Example red flags:
struct AppState { current_user: Mutex<Option<User>> }*state.current_user.lock().unwrap() = user;in middlewarelet user = state.current_user.lock().unwrap();in a handler
Dynamic Testing: This issue requires authenticated testing with multiple user accounts to detect data leakage. middleBrick's standard unauthenticated scan cannot reliably trigger it, but the Data Exposure check may flag endpoints returning sensitive data without authentication—a potential symptom. For definitive detection, use middleBrick's CLI tool in a scripted test with two user sessions:
# Scan as user1
middlebrick scan https://api.example.com/user/profile --auth-header "Bearer $TOKEN1"
# Scan as user2
middlebrick scan https://api.example.com/user/profile --auth-header "Bearer $TOKEN2"
# Compare responses for cross-user dataIf user2's response contains user1's private data, Zone Transfer is present. middleBrick's GitHub Action can automate such tests in CI/CD, failing builds if leakage is detected.
Axum-Specific Remediation
The fix is to use Axum's Extensions for per-request data. Extensions are a type-map attached to the request, isolated per-request. Shared State should only hold immutable, application-wide data (config, pools).
Step 1: In middleware, extract the State (if needed for shared resources) and insert request-scoped data into req.extensions_mut():
use axum::{extract::{Request, State, Extension}, http::StatusCode, response::Response, middleware::Next};
use std::sync::Arc;
struct AppState {
db_pool: DbPool, // Shared, immutable data
}
struct AuthUser {
id: String,
// ...
}
async fn auth_middleware(
mut req: Request<axum::body::Body>,
next: Next<State<Arc<AppState>>>,
) -> Response {
let user = authenticate(&req).await; // Returns AuthUser
// Store per-request data in extensions (safe, per-request)
req.extensions_mut().insert(user);
next.run(req).await
}Step 2: In handlers, extract the data via Extension:
async fn handler(
Extension(user): Extension<AuthUser>, // Per-request, isolated
State(state): State<Arc<AppState>>, // Shared, safe
) -> Response {
// Use user.id and state.db_pool safely
let data = fetch_user_data(&state.db_pool, &user.id).await;
Response::new(axum::body::Body::from(serde_json::to_vec(&data).unwrap()))
}This pattern ensures no data crosses request boundaries. Also, audit all State definitions: if a field is request-specific, move it to Extensions. Use middleBrick's Dashboard to track scores after remediation—Zone Transfer fixes should improve the Data Exposure and Authentication category scores.