Xml External Entities in Axum with Dynamodb
Xml External Entities in Axum with Dynamodb — how this specific combination creates or exposes the vulnerability
XML External Entity (XXE) injection occurs when an application processes XML input that references external entities, allowing an attacker to force the parser to disclose local files, trigger SSRF, or consume resources. In an Axum application that accepts XML payloads and interacts with Amazon DynamoDB, the combination of an XML parser configured to resolve external references and DynamoDB operations can expose sensitive data or alter runtime behavior.
Consider an endpoint that receives an XML document containing entity definitions and uses that data to build a DynamoDB request, such as constructing a table name or filter condition from parsed XML text. If the XML parser resolves external entities, an attacker-supplied DOCTYPE can cause the parser to read files like /etc/passwd or reach internal metadata services. The parsed values then flow into DynamoDB operations, for example when using aws_sdk_dynamodb to get or query items. Because the SDK is called with values derived from the XML, malicious entities can indirectly influence which data is accessed or how requests are formed, creating an indirect XXE-to-DynamoDB path.
An illustrative Axum handler that is vulnerable might deserialize XML into a struct and directly use a field as a table name:
use aws_sdk_dynamodb::Client;
use axum::{routing::post, Router};
async fn handle_xml(
body: String,
client: &Client,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let parsed: MyXml = quick_xml::de::from_str(&body).map_err(|e| {
(StatusCode::BAD_REQUEST, format!("XML parse error: {e}"))
})?;
let table_name = parsed.table_name; // derived from XML
let output = client.get_item()
.table_name(&table_name)
.key("id", aws_sdk_dynamodb::types::AttributeValue::S("123".to_string()))
.send()
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(output)
}
In this scenario, if quick_xml is configured to process external entities, an attacker can supply an XML payload with a malicious DOCTYPE that reads server-side files or forces outbound HTTP requests. The extracted content can manipulate table_name, leading to unauthorized data access or SSRF against internal DynamoDB endpoints. Even when not directly using XML to build queries, deserializing untrusted XML and passing values to DynamoDB clients can propagate unsafe data through the application.
Because middleBrick tests unauthenticated attack surfaces and includes input validation and SSRF checks among its 12 parallel security checks, it can flag such combinations. The scan does not fix the issue but provides findings with severity, impact context, and remediation guidance to help you harden the integration between XML parsing and DynamoDB usage in Axum.
Dynamodb-Specific Remediation in Axum — concrete code fixes
Remediation focuses on preventing untrusted data from reaching DynamoDB operations and disabling external entity resolution in XML parsing. For Axum handlers that work with XML input, avoid parsing XML with external entity expansion enabled. If XML processing is required, use a parser configured to ignore DOCTYPE and external references, or switch to a safer data format like JSON.
When constructing DynamoDB requests, validate and sanitize all inputs. Do not directly use parsed XML fields as table names or key values. Instead, map them to a controlled set of identifiers or use strict allowlists. Below are concrete Axum handler examples that follow secure patterns.
1) Use JSON instead of XML to avoid XXE entirely:
use aws_sdk_dynamodb::Client;
use axum::{routing::post, Json, Router};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct ItemRequest {
table_suffix: String,
id: String,
}
async fn handle_json(
Json(payload): Json<ItemRequest>,
client: &Client,
) -> Result<impl IntoResponse, (StatusCode, String)> {
// Allowlist validation: only accept known suffixes to prevent arbitrary table access
const ALLOWED_SUFFIXES: &[&str] = &["users", "orders"];
if !ALLOWED_SUFFIXES.contains(&payload.table_suffix.as_str()) {
return Err((StatusCode::BAD_REQUEST, "invalid table suffix".to_string()));
}
let table_name = format!("app_{}", payload.table_suffix);
let output = client.get_item()
.table_name(&table_name)
.key("id", aws_sdk_dynamodb::types::AttributeValue::S(payload.id))
.send()
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Json(output))
}
2) If XML is mandatory, configure the parser to disable external entities. For example, using the xml-rs crate with a custom reader that does not resolve entities:
use aws_sdk_dynamodb::Client;
use axum::{routing::post, Body, Request, Response};
use std::io::BufReader;
async fn safe_xml_handler(
body: String,
client: &Client,
) -> Result<impl IntoResponse, (StatusCode, String)> {
// Use a reader that does not resolve external entities.
let cursor = std::io::Cursor::new(body.into_bytes());
let mut reader = BufReader::new(cursor);
// Parse with an event-based approach that ignores DOCTYPE and entities.
let mut parser = xml_rs::reader::EventReader::new(&mut reader);
let mut table_suffix = None;
for e in parser.by_ref() {
match e.map_err(|_| (StatusCode::BAD_REQUEST, "XML parse error"))? {
xml_rs::reader::XmlEvent::StartElement { name, attributes, .. } if name.local_name == "table" => {
for attr in attributes {
if attr.name.local_name == "suffix" {
table_suffix = Some(attr.value);
}
}
}
xml_rs::reader::XmlEvent::EndElement { name } if name.local_name == "request" => break,
_ => {}
}
}
let suffix = table_suffix.ok_or_else(|| (StatusCode::BAD_REQUEST, "missing table suffix".to_string()))?;
// Strict allowlist check before using in DynamoDB request
const ALLOWED: &[&str] = &["users", "logs"];
if !ALLOWED.contains(&suffix.as_str()) {
return Err((StatusCode::BAD_REQUEST, "disallowed table suffix".to_string()));
}
let output = client.get_item()
.table_name(&format!("app_{}", suffix))
.key("id", aws_sdk_dynamodb::types::AttributeValue::S("123".to_string()))
.send()
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Response::new(Body::from(format!("table: {}", output.table_name().unwrap_or_default()))))
}
3) Apply defense-in-depth by validating and sanitizing all data before constructing DynamoDB calls. Use structured data formats, allowlists, and avoid reflecting raw XML/JSON content into control plane operations like table or index names. By removing external entity resolution and strictly controlling how values reach the AWS SDK for Rust (aws_sdk_dynamodb), you reduce the risk of injection paths that combine XML parsing with DynamoDB operations.
middleBrick can support your remediation workflow: the CLI (scan from terminal with middlebrick scan <url>), the GitHub Action (add API security checks to your CI/CD pipeline), and the Web Dashboard (track your API security scores over time) can help you monitor and manage risk as you implement fixes. The findings include prioritized guidance so you can address the highest-impact issues first.