Prototype Pollution in Axum with Api Keys
Prototype Pollution in Axum with Api Keys — how this specific combination creates or exposes the vulnerability
Prototype pollution in Axum typically arises when user-controlled input modifies the properties of shared objects used to construct API key validation logic. In Axum, route handlers often deserialize JSON payloads into structs and may merge user-supplied data into configuration or context objects that later influence how API keys are parsed, stored, or compared. If the application uses serde to deserialize untrusted data into a mutable struct that is later used to build authorization checks, an attacker can inject properties such as __proto__, constructor.prototype, or other special keys that alter object behavior in subsequent operations.
Consider an endpoint that accepts filter parameters for selecting which API keys to return. If the handler deserializes query or body input into a generic map and then applies that map to modify a base configuration struct, an attacker can supply {"constructor": {"prototype": {"isAdmin": true}}}. When Axum merges this into a permissions object or a key-validation context, the polluted prototype may cause the handler to treat a regular request as authorized, bypassing intended API key checks. This becomes especially dangerous when the polluted data influences string comparisons or hashing routines used to validate keys, potentially allowing an attacker to bypass authentication without knowing a valid key.
Another scenario involves dynamic key derivation where Axum handlers use user input to construct key names or scopes. If an attacker can pollute the prototype of objects used in this derivation, they may force the handler to compare keys against attacker-controlled values or to include additional properties in the key material. Because API keys are often handled as strings or simple structs, polluted prototypes can change equality semantics, leading to unintended matches. The risk is compounded when the handler relies on third-party crates that perform deep equality checks on objects derived from request data.
Because middleBrick scans the unauthenticated attack surface, it can detect endpoints where API key handling logic is influenced by deserialized user input. Findings typically highlight missing input validation, unsafe merging of JSON into configuration structs, and exposure of sensitive authorization logic. Remediation focuses on strict schema validation, avoiding prototype-mutable deserialization targets, and isolating key validation logic from generic user input.
Api Keys-Specific Remediation in Axum — concrete code fixes
To secure API key handling in Axum, ensure that user input never reaches the structures used for key validation. Use strongly typed, non-mergeable structs for configuration and validate all inputs against an explicit schema before they influence key logic. The following examples demonstrate safe patterns.
1. Strict deserialization with a dedicated config struct
Define a configuration struct that only includes expected fields and deserialize directly into it. This prevents prototype pollution by ignoring unexpected keys.
use axum::extract::Query;
use serde::Deserialize;
#[derive(Deserialize)]
struct ApiKeyQuery {
key: String,
}
async fn handler(Query(params): Query<ApiKeyQuery>) -> String {
// Use params.key safely; unknown fields are rejected
format!("Authorized: {}", params.key)
}
2. Reject mutable merges and use serde_json::Value cautiously
If you must work with dynamic JSON, avoid merging into mutable maps or objects that could have a polluted prototype. Instead, validate each field explicitly.
use axum::Json;
use serde_json::Value;
async fn dynamic_handler(Json(body): Json<Value>) -> String {
let key = body.get("key")
.and_then(|v| v.as_str())
.ok_or_else(|| axum::http::StatusCode::BAD_REQUEST)?;
// Validate key format before using it in authorization logic
if key.len() != 64 {
return Err(axum::http::StatusCode::BAD_REQUEST);
}
format!("Valid key submitted")
}
3. Isolate key validation from request-derived objects
Keep authorization logic separate from request deserialization. Do not reuse request structs for configuration or permission checks that involve API keys.
use axum::extract::State;
use std::sync::Arc;
struct AppState {
valid_keys: std::collections::HashSet<String>
}
async fn auth_handler(
State(state): State<Arc<AppState>>,
Json(payload): Json<serde_json::Value>
) -> Result<(), axum::http::StatusCode> {
let key = payload.get("api_key")
.and_then(|v| v.as_str())
.ok_or(axum::http::StatusCode::UNAUTHORIZED)?;
if state.valid_keys.contains(key) {
Ok(())
} else {
Err(axum::http::StatusCode::UNAUTHORIZED)
}
}
4. Use crates that avoid prototype-mutable conversions
Prefer deserialization crates or configurations that do not expose prototype pollution vectors. Avoid using raw serde_json::Map for merging user input into authorization contexts.
Frequently Asked Questions
How does middleBrick detect prototype pollution risks in Axum API key handling?
__proto__ and constructor.prototype. It correlates OpenAPI/Swagger specs with runtime observations to identify points where user-controlled data can influence authorization objects used for API key validation.