Xml Bomb in Actix (Rust)
Xml Bomb in Actix with Rust — how this specific combination creates or exposes the vulnerability
An XML bomb (also known as a billion laughs attack) exploits nested entity expansion in XML parsers to consume excessive memory and CPU. When Actix-web services accept XML payloads and deserialize them using typical Rust XML crates, the combination of recursive entity expansion and Actix’s asynchronous request handling can lead to resource exhaustion. This occurs because the XML parser, often configured with default or permissive entity settings, expands deeply nested entities into very large in-memory structures before application logic runs.
In Rust, common crates like xml-rs or quick-xml can be configured to support external entities and DTDs. If entity expansion is not explicitly disabled, an attacker can send a small payload that expands to gigabytes of data. Because Actix processes requests asynchronously, a single malicious XML body can saturate worker threads and memory, degrading or crashing the service. The risk is higher when XML deserialization is used in endpoints that do not enforce size limits or entity expansion controls, effectively turning a benign Actix service into an easy denial-of-service vector.
middleBrick scans such endpoints in black-box mode, testing for behaviors consistent with XML entity expansion and excessive resource consumption. Although middleBrick does not fix or block the issue, its findings include severity-rated detection of unsafe XML consumption patterns and remediation guidance mapped to relevant frameworks such as OWASP API Top 10 and CWE-400 (Uncontrolled Resource Consumption).
Rust-Specific Remediation in Actix — concrete code fixes
To mitigate XML bombs in Actix services written in Rust, you must configure your XML parser to disable DTDs and external entity processing, and enforce strict size limits. Below are concrete, idiomatic examples using quick-xml and serde-xml-rs, two widely used crates.
1. Using quick-xml safely
Configure Reader to not process namespaces or external entities, and limit buffer size to avoid unbounded memory growth.
use quick_xml::events::Event;
use quick_xml::Reader;
use std::io::Cursor;
fn parse_xml_safely(input: &[u8]) -> Result<(), String> {
let mut reader = Reader::from_reader(input);
// Disable expansion of entities and DOCTYPE declarations
reader.trim_text(true);
reader.check_comments(false);
reader.check_end_names(false);
reader.check_entity_attributes(false);
// Enforce a reasonable read buffer cap to limit memory usage
const MAX_BUFFER: usize = 1_048_576; // 1 MB
let mut buf = Vec::with_capacity(128);
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Eof) => break,
Ok(_) => { /* process safe events */ }
Err(e) => return Err(format!("XML parse error: {}", e)),
}
buf.clear();
}
Ok(())
}
// Actix handler example
use actix_web::{post, web, HttpResponse};
#[post("/safe-parse")]
async fn safe_parse(xml_body: String) -> HttpResponse {
match parse_xml_safely(xml_body.as_bytes()) {
Ok(_) => HttpResponse::Ok().body("XML accepted"),
Err(e) => HttpResponse::BadRequest().body(e),
}
}
2. Using serde-xml-rs with controlled features
Ensure your dependencies do not enable dangerous features. In Cargo.toml, prefer a strict configuration:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = { version = "0.5", default-features = false, features = ["deserialize"] }
Then use a bounded deserialization path:
use serde::Deserialize;
use serde_xml_rs::from_reader;
use std::io::Cursor;
use actix_web::{post, web, HttpResponse};
#[derive(Deserialize, Debug)]
struct SafeItem {
id: u32,
name: String,
}
fn parse_limited_xml<T: for<'de> Deserialize<'de>>(data: &[u8]) -> Result<T, String> {
// Limit the reader size to prevent large payloads
const MAX_XML_SIZE: usize = 1_048_576; // 1 MB
let cursor = Cursor::new(data);
// Deserialize with a reader that does not expand entities if the crate respects parser config
from_reader(cursor).map_err(|e| format!("Deserialization failed: {}", e))
}
#[post("/limited-parse")]
async fn limited_parse(xml_body: String) -> HttpResponse {
if xml_body.len() > MAX_XML_SIZE {
return HttpResponse::PayloadTooLarge().body("Payload too large");
}
match parse_limited_xml::<SafeItem>(xml_body.as_bytes()) {
Ok(_) => HttpResponse::Ok().body("Parsed safely"),
Err(e) => HttpResponse::BadRequest().body(e),
}
}
Additional operational guidance: place an HTTP-level size limit at the Actix server or reverse proxy, and avoid enabling features that process external DTDs. Combine these code-level controls with runtime monitoring to detect anomalous request patterns that may indicate an XML bomb attempt.