Nosql Injection in Axum with Dynamodb
Nosql Injection in Axum with Dynamodb — how this specific combination creates or exposes the vulnerability
NoSQL injection in an Axum service that uses DynamoDB typically occurs when user-controlled input is directly interpolated into DynamoDB expression attribute values or key condition expressions rather than being passed as structured parameter values. In Axum, this often happens in handler code that builds QueryInput or ScanInput from request parameters (e.g., path, query, or JSON body) and supplies them to the DynamoDB client. Because DynamoDB’s query syntax includes comparison operators and expression attribute names/values, unsanitized input can change expression structure, leading to unintended access to items, data exposure, or bypass of intended partition key checks.
Consider an endpoint that retrieves a user profile by user_id and an optional filter on a string attribute such as status. A vulnerable Axum handler might concatenate the status value directly into an expression string:
// Unsafe: status interpolated into expression
let expr = format!("#status = :status_val");
let input = QueryInput {
table_name: "users".to_string(),
key_condition_expression: Some(format!("user_id = :uid")),
expression_attribute_names: Some({
let mut m = HashMap::new();
m.insert("#status".to_string(), "status".to_string());
m
}),
expression_attribute_values: Some({
let mut m = HashMap::new();
m.insert(":status_val".to_string(), AttributeValue::S(status.to_string()));
m.insert(":uid".to_string(), AttributeValue::S(user_id));
m
}),
..Default::default()
};
If the status value contains a DynamoDB expression fragment (e.g., "active OR #status = :s"), an attacker can manipulate which items are returned or cause a scan-like behavior depending on how the expression is composed. More critically, if the partition key is derived or partially influenced by attacker input without strict validation, injection can lead to querying different partitions and accessing other users’ data, effectively a BOLA/IDOR vector enabled by NoSQL injection.
DynamoDB’s scan and query APIs do not enforce schema-level type safety on expressions; they evaluate strings as code-like expressions. Therefore, Axum handlers must treat all user input as untrusted and avoid building expression components through string concatenation or interpolation. Instead, use DynamoDB’s structured expression attribute names and values exclusively, and validate and encode input against an allowlist where feasible. This aligns with the broader category of Injection within the OWASP API Top 10 and appears in many real-world findings from continuous scans of Rust-based services.
Dynamodb-Specific Remediation in Axum — concrete code fixes
Remediation focuses on two controls: (1) never embed user input into expression strings, and (2) validate and constrain input values before using them as expression attribute values or keys. Below are concrete Axum handler examples that demonstrate a safe pattern.
Safe query with strict partition key and validated attribute values
Use only parameterized expression attribute names and values. Keep the partition key fixed from trusted route data and do not allow it to be overridden by user input:
use aws_sdk_dynamodb::types::AttributeValue;
use axum::{routing::get, Router};
use std::collections::HashMap;
async fn get_user_profile(
user_id: String,
query_status: Option,
dynamodb_client: &aws_sdk_dynamodb::Client,
) -> Result, aws_sdk_dynamodb::types::SdkError> {
let mut expression_attribute_names = HashMap::new();
expression_attribute_names.insert("#st".to_string(), "status".to_string());
let mut expression_attribute_values = HashMap::new();
expression_attribute_values.insert(":uid".to_string(), AttributeValue::S(user_id));
let mut request = dynamodb_client.query()
.table_name("users")
.key_condition_expression("user_id = :uid")
.expression_attribute_names(&expression_attribute_names);
if let Some(status) = query_status {
// Validate status against an allowlist
if !matches!(status.as_str(), "active" | "inactive" | "pending") {
return Err(aws_sdk_dynamodb::types::SdkError::ConstructionFailure(
"invalid status value".into(),
));
}
request = request
.filter_expression("#st = :st_val")
.expression_attribute_values({
let mut m = expression_attribute_values.clone();
m.insert(":st_val".to_string(), AttributeValue::S(status));
m
});
}
let output = request.send().await?;
Ok(output.items().unwrap_or_default().to_vec())
}
Scan with parameterized filter and allowlist validation
For operations that need to filter on non-key attributes, use a parameterized filter expression and strict validation. Do not allow raw expression fragments from the client:
async fn list_items(
owner_id: String,
maybe_type: Option,
client: &aws_sdk_dynamodb::Client,
) -> Result>, Box> {
let mut expression_attribute_names = HashMap::new();
expression_attribute_names.insert("#owner".to_string(), "owner_id".to_string());
let mut expression_attribute_values = HashMap::new();
expression_attribute_values.insert(":oid".to_string(), AttributeValue::S(owner_id));
let mut request = client.scan()
.table_name("items")
.filter_expression("#owner = :oid")
.expression_attribute_names(&expression_attribute_names)
.expression_attribute_values(expression_attribute_values.clone());
if let Some(item_type) = maybe_type {
// Strict allowlist prevents injection through type
if !matches!(item_type.as_str(), "file" | "queue" | "log") {
return Err("invalid item_type".into());
}
request = request.filter_expression("#owner = :oid AND #t = :tval")
.expression_attribute_names({
let mut m = expression_attribute_names;
m.insert("#t".to_string(), "type".to_string());
m
})
.expression_attribute_values({
let mut m = expression_attribute_values;
m.insert(":tval".to_string(), AttributeValue::S(item_type));
m
});
}
let output = request.send().await?;
Ok(output.items().unwrap_or_default())
}
These patterns ensure that user input is confined to attribute values (or tightly constrained names) and cannot alter the structure of the expression. Combine this with runtime security scans that include NoSQL injection checks; the middleBrick CLI can be run as middlebrick scan <url> to detect such issues in unauthenticated attack surfaces, and the Pro plan supports continuous monitoring so regressions are caught early.