Header Injection in Axum with Dynamodb
Header Injection in Axum with Dynamodb — how this specific combination creates or exposes the vulnerability
Header Injection occurs when user-controlled data is reflected into HTTP headers without validation or encoding. In an Axum service that integrates with DynamoDB, this typically happens when developer code copies headers such as x-request-id, x-correlation-id, or custom headers directly into responses after reading them from incoming requests or from DynamoDB item attributes. Axum does not automatically sanitize header values; if a value contains newline characters (e.g., \r\n) or is otherwise malformed, it can break header structure and enable injection attacks like header smuggling, response splitting, or cache poisoning.
When the application fetches an item from DynamoDB and uses an attribute as a header value, the risk becomes concrete. For example, a table storing user profiles might have a custom_header attribute that is later used in Axum’s response construction. If an attacker can inject \r\nSet-Cookie: auth=steal into that DynamoDB attribute, and Axum reflects it unchecked into outgoing headers, the injected header can alter the semantics of the HTTP transaction. Because DynamoDB stores strings as provided, and Axum trusts them, the boundary between data and control channel blurs, creating a server-side injection vector.
This combination also amplifies risks when headers influence behavior beyond reflection. An attacker might manipulate header-based routing or feature flags stored in DynamoDB, causing Axum to activate unsafe code paths. Even when responses are not directly returned to the attacker, injected headers can affect intermediaries or logging systems that process raw HTTP streams. The 12 checks in middleBrick include Input Validation and Property Authorization, which help surface these issues by analyzing how headers and DynamoDB-derived data are handled in the runtime surface.
Dynamodb-Specific Remediation in Axum — concrete code fixes
Remediation centers on strict validation, canonicalization, and avoiding the use of untrusted data as HTTP headers. When integrating DynamoDB with Axum, treat all item attributes as untrusted input. Do not pass DynamoDB string attributes directly into header constructors. Instead, use a strict allowlist approach for known-safe values or transform the data into a safe representation.
Below is a concrete Axum handler that demonstrates a vulnerable pattern and a remediated version. The vulnerable example reads a X-Custom-Header from an incoming request, stores it in a DynamoDB item, and later uses the stored value in a response header. The remediated version validates and sanitizes the value before it ever reaches headers or DynamoDB.
// Vulnerable pattern (do not use)
use axum::{routing::get, Router, http::HeaderValue};
use aws_sdk_dynamodb::Client as DynamoDbClient;
async fn vulnerable_handler(
HeaderValue(req_header): HeaderValue,
client: &DynamoDbClient,
) -> Result {
// Store user-supplied header value into DynamoDB (simplified)
client.put_item()
.table_name("AppConfig")
.item("header_value", aws_sdk_dynamodb::types::AttributeValue::S(req_header.to_string()))
.send()
.await
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
// Later, reflect stored value back into a response header
let resp = axum::response::Response::builder()
.header("X-Stored", req_header)
.body(axum::body::Body::empty())
.unwrap();
Ok(resp)
}
The issues include: using HeaderValue::from_str implicitly via Axum’s extractor without checking for newlines, storing raw strings in DynamoDB, and reflecting them unchecked into response headers. An attacker can send \r\nX-Injected: true and corrupt the header block.
Remediated version with validation and safe usage:
use axum::{routing::get, Router, http::HeaderValue};
use aws_sdk_dynamodb::Client as DynamoDbClient;
use header::HeaderName;
fn safe_header_value(s: &str) -> Option {
// Reject newlines and control characters per RFC 7230
if s.contains('\n') || s.contains('\r') {
return None;
}
HeaderValue::from_str(s).ok()
}
async fn remediated_handler(
HeaderValue(req_header): HeaderValue,
client: &DynamoDbClient,
) -> Result {
// Validate before storing
let safe_value = safe_header_value(&req_header).ok_or_else(|| {
(axum::http::StatusCode::BAD_REQUEST, "Invalid header value".to_string())
})?;
// Store canonicalized value (e.g., trimmed, safe)
client.put_item()
.table_name("AppConfig")
.item("header_value", aws_sdk_dynamodb::types::AttributeValue::S(safe_value.to_str().unwrap().to_string()))
.send()
.await
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
// Use a constant, safe header name; value already validated
let mut builder = axum::response::Response::builder()
.header("X-Stored", safe_value);
// Optionally, use HeaderName for custom header names if they are static
let resp = builder.body(axum::body::Body::empty()).unwrap();
Ok(resp)
}
Key remediation steps: validate and canonicalize header inputs with a strict allowlist (no newlines, control characters, or malformed sequences), avoid storing raw user input as header values in DynamoDB when possible, and use constant header names. If you must store and reflect, re-validate on output and prefer mapping to known-safe values. middleBrick’s Input Validation and Property Authorization checks help ensure these practices are followed across endpoints.