Dangling Dns in Actix with Basic Auth
Dangling Dns in Actix with Basic Auth — how this specific combination creates or exposes the vulnerability
A dangling DNS record occurs when a hostname (for example, legacy.api.example.com) still resolves to an IP address but is no longer backed by a service that intentionally owns that name. In Actix web applications, if runtime configuration uses a hostname with a dangling DNS record and the application enforces HTTP Basic Authentication, the combination can expose both authentication and redirection logic to an attacker.
Actix applications often load upstream hostnames from configuration or environment variables to build redirect URLs or to validate the target of a request. If the application uses Basic Auth credentials to authorize access before performing a redirect or an internal service call, and the target hostname resolves to an unexpected server, the following issues can arise:
- Open redirect via dangling hostname: An attacker who can influence the target hostname (through a compromised configuration, a subdomain takeover, or a stale DNS entry) can craft a URL such as
https://app.example.com/redirect?host=legacy.api.example.com. The Actix handler validates Basic Auth, then constructs a redirect using the attacker-controlled hostname. Because the DNS record is dangling, the hostname may resolve to an arbitrary server under attacker control, allowing an open redirect that appears to originate from a trusted domain. - Credential leakage through misconfigured upstream: If the application passes Basic Auth headers to the dangling hostname during backend calls (for example, to validate tokens or to call a downstream API), credentials may be sent to an unintended host. Although Basic Auth transmits credentials over TLS, sending them to an unknown server violates the principle of least privilege and can expose credentials if the dangling record is later claimed or if DNS caching causes requests to be misdirected.
- Confused deputy via hostname confusion: When Actix uses the same hostname for both public-facing endpoints and internal service communication, a dangling DNS record can allow an attacker to impersonate an internal service. Combined with Basic Auth, this may lead to unauthorized actions being performed with elevated privileges if the application trusts the hostname more than the actual identity of the service.
These risks are not inherent to Basic Auth or to Actix itself, but stem from insecure configuration and hostname management. The scanner category that flags this pattern is BFLA/Privilege Escalation, because misuse of hostnames in redirect or delegation logic can lead to privilege abuse.
Basic Auth-Specific Remediation in Actix — concrete code fixes
Remediation focuses on eliminating reliance on untrusted hostnames for redirects or backend calls, and on ensuring that Basic Auth is applied only to intended endpoints. Below are concrete, safe patterns for an Actix web application written in Rust.
1. Avoid redirects to dynamic hostnames
Do not construct redirects from user- or configuration-controlled hostnames. If you must redirect, use a strict allowlist of destinations.
use actix_web::{web, HttpResponse, Result};
async fn safe_redirect(target: web::Query<HashMap<String, String>>) -> Result<HttpResponse> {
// Allowlist approach: only permit known-safe paths on the current domain
if let Some(path) = target.get("path") {
if path.starts_with('/') && !path.contains("://") {
return Ok(HttpResponse::Found()
.header("Location", format!("/{}", path))
.finish());
}
}
Ok(HttpResponse::BadRequest().body("Invalid redirect target").into())
}
2. Validate upstream hostnames at configuration load time
When your Actix app loads configuration, resolve and validate hostnames against a trusted set. Fail to start if a hostname is unresolved or points to an unexpected IP range.
use reqwest::Client;
use std::net::IpAddr;
async fn validate_upstream(host: &str) -> Result<(), String> {
let resolved = host.to_socket_addrs()
.map_err(|e| format!("DNS resolution failed: {}", e))?
.collect::<Vec<std::net::SocketAddr>>();
if resolved.is_empty() {
return Err("No addresses resolved".into());
}
// Example: ensure the resolved IP is within an allowed range
for addr in resolved {
if let IpAddr::V4(ip) = addr.ip() {
if ip.octets()[0] == 10 { // allow only private range for this example
return Ok(());
}
}
}
Err("Upstream host resolves to disallowed IP".into())
}
3. Apply Basic Auth selectively to trusted routes
Use Actix's middleware to protect only the endpoints that require authentication, and avoid forwarding Basic Auth headers to dynamic upstreams.
use actix_web::dev::ServiceRequest;
use actix_web_httpauth::extractors::basic::BasicAuth;
use actix_web_httpauth::middleware::HttpAuthentication;
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
async fn authenticated_handler(_auth: BasicAuth) -> impl Responder {
HttpResponse::Ok().body("Authenticated successfully")
}
fn auth_middleware() -> HttpAuthentication {
let auth = HttpAuthentication::basic(|req, credentials| {
// Perform your user/password validation here
if credentials.userid() == "admin" && credentials.password() == "secret" {
Ok(())
} else {
Err(actix_web_httpauth::error::AuthenticationError::New("Invalid credentials"))
}
});
auth
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(
web::resource("/secure")
.route(web::get().to(authenticated_handler))
)
.wrap(auth_middleware())
})
.bind("127.0.0.1:8080")?
.run()
.await
}
4. Do not forward Basic Auth headers to untrusted hosts
If you must call a downstream service, do not forward the client's Authorization header to a hostname that is not strictly validated. Instead, use a service identity mechanism or an internal token.
use reqwest::Client;
use actix_web::web;
async fn call_upstream(data: web::Json<serde_json::Value>) -> Result<reqwest::Response, reqwest::Error> {
let client = Client::new();
// Do NOT forward Authorization header to dynamic host
let response = client
.post("https://trusted.internal/api/action")
.json(&data)
.send()
.await
}