Symlink Attack in Axum with Dynamodb
Symlink Attack in Axum with Dynamodb — how this specific combination creates or exposes the vulnerability
A symlink attack in an Axum service that uses DynamoDB for storage occurs when an application resolves file paths based on user-controlled input and writes or reads objects to a backend data store such as DynamoDB without validating path provenance. In this context, DynamoDB itself does not provide POSIX-style file semantics, but your application layer may maintain a logical mapping between object keys (e.g., user-supplied filenames or paths) and DynamoDB item attributes. If these keys are derived from untrusted input and used to construct S3-style object keys or local filesystem paths that are later resolved by the application or an auxiliary service, an attacker can supply path sequences like ../../../sensitive.txt to traverse directories or overwrite files that the application did not intend to expose.
The risk emerges from the combination of Axum routing that accepts path parameters and insecure key construction before storing metadata in DynamoDB. For example, an endpoint like /upload/{filename} may take the parameter, prepend a user ID, and store the resulting key in a DynamoDB item. Without canonicalization and strict validation, an attacker can supply ../../admin/secrets so that the composed key escapes an intended prefix, potentially allowing the application to overwrite unrelated items or expose references that downstream processes interpret as filesystem paths. Even though DynamoDB stores only item attributes, the application’s interpretation of those keys as logical paths—and any secondary process that maps them back to filesystem locations—creates a confusable vulnerability surface aligned with path traversal and symlink-based abuse patterns seen in file-handling bugs.
Because middleBrick scans the unauthenticated attack surface and tests input validation and property authorization, it can surface insecure key composition and missing path canonicalization as findings. The scanner’s input validation checks look for missing constraints on user-controlled path components, while property authorization checks examine whether item-level access controls align with the intended ownership model. If the application stores object keys in DynamoDB without verifying that the key prefix matches the authenticated subject’s namespace, middleBrick may flag authorization bypass risks and path traversal indicators tied to the API’s logical file handling.
Dynamodb-Specific Remediation in Axum — concrete code fixes
To remediate symlink-style risks in an Axum service that stores metadata in DynamoDB, enforce strict key construction, canonicalization, and ownership checks before any DynamoDB operation. Treat any user-supplied path or filename as untrusted and derive object keys using a combination of a stable subject identifier and a randomly generated suffix, avoiding direct concatenation of user input into key strings.
First, validate and sanitize input so that path traversal sequences cannot reach your key space. Use a normalization routine that removes .. segments and rejects names containing path separators. Then, map the sanitized name to a key that includes the user’s ID as a partition prefix, ensuring logical isolation between tenants.
use axum::extract::Path;
use aws_sdk_dynamodb::Client;
use uuid::Uuid;
/// Returns a safe object key for a user upload.
fn build_object_key(user_id: &str, filename: &str) -> String {
// Reject path traversal and directory components.
let base = filename.trim().trim_end_matches('/');
assert!(!base.contains(".."), "Path traversal not allowed");
assert!(!base.contains('/'), "Slashes not allowed in filename");
// Use a UUID to avoid collisions and information leakage.
let suffix = Uuid::new_v4().to_string();
format!("users/{}/{}-{}", user_id, suffix, base)
}
Second, when persisting metadata to DynamoDB, bind the key to the subject’s identity and include a condition expression to prevent accidental overwrites across namespaces:
async fn store_metadata(
client: &Client,
user_id: &str,
filename: &str,
size: i64,
) -> aws_sdk_dynamodb::result::PutItemOutput {
let key = build_object_key(user_id, filename);
client
.put_item()
.table_name("ApiUploads")
.item("pk", aws_sdk_dynamodb::types::AttributeValue::S(format!("USER#{}", user_id)))
.item("sk", aws_sdk_dynamodb::types::AttributeValue::S(key))
.item("size", aws_sdk_dynamodb::types::AttributeValue::N(size.to_string()))
.condition_expression("attribute_not_exists(pk) AND attribute_not_exists(sk)")
.send()
.await
.expect("Failed to store metadata")
}
Third, when retrieving items, enforce that queries use the same partition key derived from the authenticated subject and do not allow the client to specify the key directly. This prevents horizontal privilege escalation across users. If you must support listing by user, query only items whose key begins with the authorized prefix and apply additional authorization checks on each returned item.
async fn list_user_objects(client: &Client, user_id: &str) -> Vec<aws_sdk_dynamodb::model::Item> {
let prefix = format!("users/{}/", user_id);
let resp = client
.query()
.table_name("ApiUploads")
.key_condition_expression("pk = :p AND begins_with(sk, :s)")
.expression_attribute_values(":p", aws_sdk_dynamodb::types::AttributeValue::S(format!("USER#{}", user_id)))
.expression_attribute_values(":s", aws_sdk_dynamodb::types::AttributeValue::S(prefix))
.send()
.await
.expect("Query failed")
.items()
.cloned()
.collect();
resp
}
By combining input canonicalization, deterministic tenant-aware key construction, and strict query scoping, you eliminate the conditions that enable symlink-style path traversal when metadata is stored in DynamoDB. middleBrick’s input validation and property authorization checks can help verify that these controls are present and correctly enforced in your API surface.