HIGH xml external entitiesaxumbasic auth

Xml External Entities in Axum with Basic Auth

Xml External Entities in Axum with Basic Auth — how this specific combination creates or exposes the vulnerability

XML External Entity (XXE) injection occurs when an XML parser processes external entity references in a way that can disclose local files, trigger SSRF, or consume system resources. In Axum-based services that accept XML payloads, the risk is elevated when authentication is handled via HTTP Basic Auth and the endpoint trusts user-supplied XML without disabling external entity resolution.

Basic Auth itself does not parse XML, but it often guards an endpoint that deserializes XML bodies. If the XML parser is configured to load external DTDs or parameter entities (e.g., with features.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false) disabled in a non-secure way), an attacker can embed malicious external references. A typical vulnerable Axum handler might look like this, where Basic Auth is checked but XML parsing is permissive:

use axum::{
    body::Body,
    extract::Request,
    http::{self, Request as HttpRequest, StatusCode},
    response::Response,
    routing::post,
    Router,
};
use std::convert::Infallible;
use hyper::Body as HyperBody;

async fn auth_middleware(
    request: Request,
    credentials: (String, String), // username, password from Basic Auth header
) -> Result {
    // naive Basic Auth check
    if credentials.0 == "admin" && credentials.1 == "secret" {
        Ok(request)
    } else {
        Err((StatusCode::UNAUTHORIZED, "Invalid credentials".into()))
    }
}

async fn parse_xml(body: HyperBody) -> Result {
    let bytes = hyper::body::to_bytes(body).await.map_err(|e| e.to_string())?;
    let doc = roxmltree::Document::parse(&bytes).map_err(|e| e.to_string())?;
    // Dangerous: no prevention of external entities
    Ok(doc.root().text().unwrap_or("").to_string())
}

async fn handler(
    auth: Option<(&str, &str)>, // extracted Basic Auth
    body: hyper::Body,
) -> Result {
    let credentials = auth.ok_or((StatusCode::UNAUTHORIZED, "Missing auth".into()))?;
    auth_middleware(Request::new(Body::empty()), credentials.into()).await?;
    let result = parse_xml(body).await?;
    Ok(Response::new(Body::from(result)))
}

fn main() {
    let app = Router::new()
        .route("/api/xml", post(handler));
    // run omitted
}

In this setup, an attacker authenticated with valid Basic Auth credentials can submit an XML body such as:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [ <!ELEMENT foo ANY >
               <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>>
<foo>&xxe;</foo>

If the XML parser resolves the external entity, the server may read /etc/passwd and include its contents in the response or in logs. Even when the endpoint requires Basic Auth, the combination means an attacker who obtains or guesses a valid credential pair can weaponize XXE to move laterally within the environment. This exposes file paths, internal service metadata, and potentially secrets mounted in the container filesystem. Moreover, if the Axum service is reachable internally (e.g., within a Kubernetes pod), the same XXE vector can be used for SSRF against internal endpoints that do not expect external traffic.

Additional risks include billion laughs attacks and entity expansion when external DTDs are resolved, leading to denial of service. Because Axum does not provide built-in XML parsing, the responsibility shifts to the developer to configure the underlying parser securely. Even with Basic Auth enforcing identity, the unchecked XML deserialization remains a critical path for data exposure and server-side request forgery.

Basic Auth-Specific Remediation in Axum — concrete code fixes

Remediation focuses on two layers: strict authentication practices and secure XML parsing. For Basic Auth, avoid hardcoded credentials and prefer opaque tokens or integrating with a proper identity provider. When Basic Auth is required, validate credentials against a secure store and enforce transport security (TLS). For XML handling, disable external entity processing and DTDs entirely.

Below is a hardened Axum handler that combines safe Basic Auth verification and a secure XML parser configuration using the roxmltree crate, which does not resolve external entities by default. This reduces the attack surface significantly:

use axum::{
    body::Body,
    extract::Request,
    http::{self, header::AUTHORIZATION, Request as HttpRequest, StatusCode, HeaderMap, HeaderValue},
    response::Response,
    routing::post,
    Router,
};
use base64::Engine;
use roxmltree::Document;
use std::convert::Infallible;
use hyper::{Body as HyperBody, Request as HyperRequest};

const VALID_USER: &str = "admin";
const VALID_PASS: &str = "correct-horse-battery-staple"; // use env/secrets in production

fn validate_basic_auth(headers: &HeaderMap) -> Result<(String, String), (StatusCode, String)> {
    headers.get(AUTHORIZATION)
        .and_then(|v| v.to_str().ok())
        .and_then(|s| s.strip_prefix("Basic "))
        .and_then(|encoded| base64::engine::general_purpose::STANDARD.decode(encoded).ok())
        .and_then(|decoded| String::from_utf8(decoded).ok())
        .and_then(|cred| {
            let mut parts = cred.splitn(2, ':');
            match (parts.next(), parts.next()) {
                (Some(u), Some(p)) if u == VALID_USER && p == VALID_PASS =
                    Ok((u.to_string(), p.to_string())),
                _ => Err(()),
            }
        })
        .ok_or_else(|| (StatusCode::UNAUTHORIZED, "Invalid Basic Auth".into()))
}

fn secure_parse_xml(bytes: &[u8]) -> Result<String, (StatusCode, String)> {
    // roxmltree does not resolve external entities; safe by default
    let doc = Document::parse(bytes).map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
    // Normalize and extract only text nodes; avoid DTD/entity expansion
    Ok(doc.root().text().unwrap_or("").to_string())
}

async fn handler(
    headers: HeaderMap,
    body: HyperBody,
) -> Result<Response<Body>, (StatusCode, String)> {
    let credentials = validate_basic_auth(&headers)?;
    let body_bytes = hyper::body::to_bytes(body).await.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
    let text = secure_parse_xml(&body_bytes)?;
    Ok(Response::new(Body::from(text)))
}

fn main() {
    let app = Router::new()
        .route("/api/xml", post(handler));
    // run with axum::Server omitted
}

Key remediation points:

  • Always use TLS to prevent Basic Auth credentials from being intercepted.
  • Validate credentials against a strong secret store rather than hardcoding them in production.
  • Use XML libraries that do not process external entities by default (e.g., roxmltree in Rust) or explicitly disable DTD and external entity resolution if using other parsers.
  • Reject XML content types that include inline DOCTYPE declarations when not strictly required.
  • Apply the same secure parsing logic to JSON and other formats if your API accepts multiple serialization types.

By combining authenticated access with a parser configuration that disallows external entity resolution, you mitigate XXE while preserving legitimate XML processing needs.

Frequently Asked Questions

Can Basic Auth headers alone prevent XXE in Axum APIs?
No. Basic Auth only verifies identity; it does not alter XML parsing behavior. XXE depends on how the XML parser is configured. Even with valid Basic Auth, permissive parsers that resolve external entities or DTDs remain vulnerable.
Does enabling TLS fix XXE in Axum services using Basic Auth?
No. TLS protects credentials in transit and prevents eavesdropping, but it does not change XML parsing logic. An attacker authenticated over TLS can still submit malicious XML if the parser is not hardened against external entity resolution.