Dangling Dns in Axum
How Dangling DNS Manifests in Axum
Dangling DNS occurs when a DNS entry (like a CNAME) points to a resource that has been deprovisioned, leaving an attacker to claim that target. In Axum applications, this vulnerability often emerges in two specific patterns related to how the framework handles external service integration and dynamic routing.
Pattern 1: Unvalidated External Endpoints in Proxying Middleware
Axum applications frequently act as API gateways or reverse proxies, forwarding requests to downstream services. A common Axum code pattern uses tower::Service to make outbound HTTP calls. If the target URL is constructed from user-controlled input (e.g., a path parameter or header) without proper validation, an attacker can supply a hostname they control. If that attacker-controlled hostname later becomes a dangling DNS entry (e.g., their cloud instance is terminated), they can reclaim the IP and intercept traffic intended for the legitimate downstream service.
use axum::{routing::post, Router, http::Uri, extract::Path, response::IntoResponse};
use tower::ServiceExt;
use hyper::Client;
let app = Router::new()
.route("/proxy/:service", post(proxy_handler));
async fn proxy_handler(Path(service): Path, uri: Uri) -> impl IntoResponse {
// VULNERABLE: 'service' is user-controlled and used to build a base URL.
// An attacker could submit '/proxy/attacker-controlled-subdomain'.
let target_url = format!("https://{}.internal.api.com{}`, service, uri.path());
let client = Client::new();
let response = client.get(target_url.parse().unwrap()).await.unwrap();
response.into_response()
} Here, the :service path parameter is concatenated directly into a URL. If attacker-controlled-subdomain.internal.api.com was ever a valid CNAME pointing to an internal service that was later deleted, an attacker who registers that DNS name can now receive the proxied API traffic, including any authentication headers the Axum app forwards.
Pattern 2: Dynamic Subdomain Routing with Host-Based Extraction
Axum's Host extractor allows routing based on the HTTP Host header. Applications using multi-tenancy patterns might route tenant1.api.com and tenant2.api.com to different backends. If the mapping between a tenant identifier (from the hostname) and its backend URL is stored in a database or config that can become stale, a dangling DNS entry for a tenant's subdomain leads to a similar takeover scenario.
use axum::{routing::get, Router, extract::Host};
let app = Router::new()
.route("/", get(handler));
async fn handler(Host(hostname): Host) -> String {
// Lookup backend for this hostname in a database.
let backend_url = get_backend_url_from_db(&hostname).await;
// If the backend_url's DNS record was deleted (dangling), but the DB entry
// still exists, this request will fail or, worse, be routed to an attacker's server
// if the DNS name is reclaimed.
format!("Proxying to {}", backend_url)
}This pattern is particularly insidious because the vulnerability is not in the Axum code's logic per se, but in the operational gap between dynamic DNS/tenant management and the application's routing configuration. The OWASP API Security Top 10 lists this under API4:2023 – Unrestricted Resource Consumption when it leads to SSRF, but the core issue is a misconfigured DNS delegation that enables subdomain takeover (CWE-1284).
Axum-Specific Detection
Detecting dangling DNS in an Axum application requires analyzing both the runtime behavior (where the app makes outbound calls) and the static configuration (DNS records). A black-box scanner like middleBrick approaches this by:
- Enumerating all API endpoints from the provided OpenAPI/Swagger spec or by crawling.
- Identifying potential dynamic routing patterns. For Axum, this means looking for path parameters (e.g.,
/:service) or host-based routing that suggests external target resolution. - Testing for DNS resolution control. For each identified parameter that influences an outbound URL, middleBrick attempts to resolve the constructed domain name. If the DNS query returns a
NXDOMAINor a SERVFAIL, but the parameter value is accepted by the application (returns a 2xx/3xx/4xx, not a 5xx indicating a broken backend), it flags a potential dangling DNS vector. The scanner then correlates this with public DNS datasets to see if that name is available for registration.
This is where middleBrick's unique LLM/AI Security module adds value for Axum apps that expose AI endpoints. Axum is a common framework for serving LLM APIs (e.g., using axum::extract::Json for chat completions). If an Axum route calls an external model provider (like OpenAI, Anthropic) based on a user-supplied model parameter, and that parameter is used to construct a hostname (e.g., https://{model}.openai.com/v1), dangling DNS in the model provider's DNS infrastructure could be exploited via prompt injection to redirect exfiltration traffic. middleBrick's active prompt injection probes (CWE-94: Improper Control of Generation of Code) can test if such a redirect is possible.
Scanning with middleBrick
You can scan an Axum API endpoint directly from the terminal. The CLI tool performs the parallel checks, including the dangling DNS heuristic for dynamic routing patterns.
# Install the CLI
npm install -g middlebrick
# Scan your live Axum API endpoint
middlebrick scan https://api.youraxumapp.com
# For CI/CD integration, use the GitHub Action. This example fails the build if
# the security score drops below 'B' (80 points) or if any 'critical' findings appear.
# See the action's documentation for full syntax.
- uses: middlebrick/scan-action@v1
with:
endpoint: ${{ secrets.AXUM_API_URL }}
fail_below_score: 80
fail_on_severity: criticalThe resulting report will include a per-category breakdown. A dangling DNS finding will appear under a category like Resource Exposure or SSRF, with a severity rating (High/Critical) and specific paths/parameters involved, such as GET /proxy/:service.
Axum-Specific Remediation
Remediation in Axum centers on strict validation and allowlisting. Never trust user input for constructing outbound URLs or hostnames.
1. Validate and Allowlist Dynamic Parameters
If your Axum route uses a path parameter to select a backend service, replace the free-form string with a validated enum or a lookup against a server-side allowlist. Use Axum's extractors to perform this validation before the handler logic.
use axum::{routing::post, Router, extract::Path};
// Define a strict allowlist of known backend services.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BackendService {
Users,
Orders,
Inventory,
}
impl BackendService {
fn from_str(s: &str) -> Option {
match s {
"users" => Some(Self::Users),
"orders" => Some(Self::Orders),
"inventory" => Some(Self::Inventory),
_ => None,
}
}
fn base_url(&self) -> &'static str {
match self {
Self::Users => "https://users.internal.api.com",
Self::Orders => "https://orders.internal.api.com",
Self::Inventory => "https://inventory.internal.api.com",
}
}
}
// Custom extractor that validates the path parameter.
struct ValidatedService(BackendService);
impl FromRequestParts for ValidatedService
where
S: Send + Sync,
{
type Rejection = (axum::http::StatusCode, String);
async fn from_request_parts(
parts: &mut axum::http::request::Parts,
state: &S,
) -> Result {
let Path(service) = Path::::from_request_parts(parts, state).await?;
BackendService::from_str(&service)
.ok_or((axum::http::StatusCode::BAD_REQUEST, format!("Invalid service: {}", service)))
.map(ValidatedService)
}
}
let app = Router::new()
.route("/proxy/:service", post(proxy_handler));
async fn proxy_handler(ValidatedService(service): ValidatedService, uri: Uri) -> impl axum::response::IntoResponse {
let target_url = format!("{}{}", service.base_url(), uri.path());
// ... safe proxy logic using the validated, static base URL
} 2. Enforce Host Header Validation
For host-based routing, use Axum's Host extractor but immediately validate it against a server-side allowlist of expected tenant domains. Reject any request with an unexpected Host header early.
use axum::{routing::get, Router, extract::Host};
// A set of allowed tenant hostnames (could be loaded from config/DB at startup).
const ALLOWED_TENANTS: [&str; 2] = ["tenant1.api.com", "tenant2.api.com"];
struct TenantHost(String);
impl FromRequestParts for TenantHost
where
S: Send + Sync,
{
type Rejection = (axum::http::StatusCode, &'static str);
async fn from_request_parts(
parts: &mut axum::http::request::Parts,
_state: &S,
) -> Result {
let Host(hostname) = Host::from_request_parts(parts, _state).await?;
if ALLOWED_TENANTS.contains(&hostname.as_str()) {
Ok(TenantHost(hostname.to_string()))
} else {
Err((axum::http::StatusCode::BAD_REQUEST, "Invalid tenant hostname"))
}
}
}
let app = Router::new()
.route("/", get(handler));
async fn handler(TenantHost(hostname): TenantHost) -> String {
// At this point, 'hostname' is guaranteed to be in the allowlist.
let backend_url = get_backend_url_for_tenant(&hostname).await;
format!("Proxying to {}", backend_url)
} 3. Use DNS Pinining for Critical Outbound Calls
For high-stakes proxy calls where the backend IP must be stable, consider DNS pinning: resolve the backend hostname to an IP at application startup and use the IP in the outbound request, bypassing DNS for each request. This is an advanced mitigation that trades off flexibility for security and must be balanced with backend IP change management.
4. Integrate Scanning into CI/CD
Use the middleBrick GitHub Action to automatically scan your staging Axum API on every deploy. Configure it to fail the build if a dangling DNS (or any high-severity) finding is detected, preventing vulnerable configurations from reaching production.
| Remediation Approach | Axum Feature Used | Pros | Cons |
|---|---|---|---|
| Static Allowlist | Custom FromRequestParts extractor | Simple, performant, eliminates dynamic DNS risk | Loses dynamic tenant onboarding flexibility |
| Dynamic DB Lookup with Cache | Async handler with cached allowlist | Supports dynamic tenant management | Cache invalidation complexity; still vulnerable if DB is stale |
| DNS Pinning | Manual IP-based outbound calls | Immunizes against DNS changes/takeovers | Breaks if backend IP changes; requires ops coordination |
FAQ
Q: Could middleBrick report a false positive for dangling DNS if my Axum API's dynamic routing is intentional and the backend is stable?
A: Yes, the scanner's heuristic identifies patterns where user input influences outbound hostnames. If your use case is legitimate (e.g., a multi-tenant proxy with a robust, real-time tenant registry), the finding is a signal to review your validation logic. The remediation section's allowlist pattern is the recommended fix. middleBrick's scoring weighs the presence of such patterns higher when combined with other risk factors like missing authentication.
Q: Does the middleBrick scan for dangling DNS require credentials to access my Axum API's internal routing logic?
A: No. middleBrick is a black-box scanner. It analyzes the publicly accessible API surface—the endpoints you can reach without authentication. It infers potential dangling DNS vectors by examining how the API accepts parameters and attempts to resolve derived domain names from the outside. This is exactly how an external attacker would probe your API. For internal-only Axum services, you would need to scan a publicly accessible staging environment or use the CLI tool within your network.
Meta Description
Learn how dangling DNS vulnerabilities specifically manifest in Rust Axum applications through proxy patterns and host-based routing, and how to detect and fix them.
Risk Summary
Severity: High. Category: Resource Exposure / Subdomain Takeover. Dangling DNS in an Axum API can lead to complete traffic interception, data exfiltration, and credential theft if an attacker reclaims the DNS name pointing to a deprovisioned backend service. This directly violates PCI-DSS requirement 8.3 (secure authentication) and can lead to GDPR Article 32 breaches due to unauthorized data access.