Dangling Dns in Axum (Rust)
Dangling DNS in Axum with Rust — how this specific combination creates or exposes the vulnerability
Dangling DNS occurs when an API endpoint references a domain in its configuration (e.g., for external service calls, webhook URLs, or redirect logic) that points to a DNS record which no longer resolves to an owned or controlled resource. In the context of Axum, a Rust-based web framework, this vulnerability often arises in middleware, route handlers, or state shared across requests where domain names are dynamically resolved or used in outbound HTTP calls without validation.
For example, consider an Axum handler that forwards requests to a third-party service based on a user-provided subdomain, using a base domain configured at startup. If the DNS record for that subdomain is deleted or expires (e.g., a marketing subdomain like promo.example.com pointing to a now-decommissioned external service), an attacker can register the dangling domain and intercept or manipulate traffic intended for the original service.
In Axum applications, this risk is heightened when:
- Configuration values (like
BASE_EXTERNAL_URL) are loaded once at startup and never re-validated. - Outbound HTTP clients (e.g., using
reqwest) are constructed with string-concatenated URLs that include user-influenced host parts. - Redirects or webhook validations rely on domain matching without checking DNS ownership or using allowlists.
Unlike server-side flaws such as SQL injection, dangling DNS is a runtime dependency issue. Axum’s async, type-safe nature doesn’t prevent it because the vulnerability lies in external trust assumptions, not code logic. The framework’s strength in compile-time safety does not extend to runtime DNS validity, making configuration hygiene critical.
Real-world parallels include CVE-2021-30465 (Azure Cloud Shell domain takeover) and OWASP API Security Top 10 2023’s A01:2023 – Broken Object Property Authorization, where trust in external references leads to indirect access control bypass. middleBrick detects such risks by scanning for outbound calls to unresolved or externally controllable domains during its unauthenticated attack surface analysis, flagging them under 'Data Exposure' or 'Unsafe Consumption' categories.
Rust-Specific Remediation in Axum — concrete code fixes
Mitigating dangling DNS in Axum requires validating external domains at runtime and avoiding dynamic trust in DNS-resolved endpoints. Rust’s ownership and type system enable compile-time guarantees for configuration handling, but runtime validation must be explicit.
First, avoid hardcoding or concatenating user input into URLs for outbound requests. Instead, use structured configuration with validation at load time. For example, use the config crate to load and validate settings:
use config::{Config, File};
use std::net::IpAddr;
#[derive(Debug, serde::Deserialize)]
struct Settings {
external_api_base: String, // e.g., "https://api.trustedpartner.com"
}
impl Settings {
fn validate(&self) -> Result<(), String> {
// Parse URL and validate host is IP or allowlisted domain
let url = url::Url::parse(&self.external_api_base)
.map_err(|e| format!("Invalid URL: {}", e))?;
let host = url.host_str().ok_or_else(|| "Missing host".to_string())?;
// Option 1: Allowlist known domains
let allowed = ["api.trustedpartner.com", "webhook.trustedpartner.com"];
if !allowed.iter().any(|&d| host == d || host.ends_with(&format!(".{}", d))) {
return Err(format!("Host '{}' not in allowlist", host));
}
// Option 2: Reject IPs to prevent SSRF via DNS rebinding (optional)
if host.parse::().is_ok() {
return Err("IP addresses not allowed in external API base".to_string());
}
Ok(())
}
}
let settings = Config::builder()
.add(File::with_name("Settings"))
.build()?
.try_deserialize::()?;
settings.validate()?; // Fail fast if misconfigured
Second, when making outbound requests (e.g., with reqwest), isolate the HTTP client and validate hosts per request if dynamic values are unavoidable:
use axum::extract::State;
use axum::http::StatusCode;
use reqwest::Url;
async fn forward_request(
State(client): State,
State(settings): State,
axum::Json(payload): axum::Json,
) -> Result, (StatusCode, String)> {
let target_url = format!("{}/webhook", settings.external_api_base);
let parsed = Url::parse(&target_url)
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid target URL: {}", e)))?;
// Re-validate host at request time (defense in depth)
let host = parsed.host_str().ok_or_else(|| (
StatusCode::INTERNAL_SERVER_ERROR,
"Missing host in target URL".to_string(),
))?;
if !["api.trustedpartner.com"].contains(&host) {
return Err((
StatusCode::FORBIDDEN,
format!("Outbound request to unauthorized host: {}", host),
));
}
let response = client
.post(parsed.as_str())
.json(&payload)
.send()
.await
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
let body = response.json().await.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
Ok(axum::Json(body))
}
Finally, leverage middleBrick’s scanning to detect risky outbound calls in your Axum API. Submit your public URL via the dashboard, CLI (middlebrick scan https://your-api.com), or GitHub Action to identify dangling DNS risks under 'Unsafe Consumption' or 'Data Exposure' findings, with remediation guidance pointing to validate or allowlist external domains.