Dns Cache Poisoning in Axum
How Dns Cache Poisoning Manifests in Axum
DNS cache poisoning in Axum applications typically occurs when the framework's HTTP client or DNS resolver is used without proper validation and timeout controls. Axum's default Tokio runtime uses the system's DNS resolver, which can be vulnerable to cache poisoning attacks if not properly configured.
The most common manifestation in Axum applications is through the http::Uri handling in extractors and middleware. When an attacker controls DNS responses, they can redirect API calls to malicious servers, leading to data exfiltration or supply chain attacks. This is particularly dangerous in microservices architectures where Axum services communicate with each other.
Consider this vulnerable pattern in Axum:
use axum::extract::TypedHeader;
use axum::http::header::HeaderMap;
use axum::http::Uri;
use axum::response::IntoResponse;
use axum::routing::get;
use axum::Router;
async fn proxy_request(
TypedHeader(headers): TypedHeader,
) -> impl IntoResponse {
// Vulnerable: No DNS validation or timeout
let uri = headers.get("x-forwarded-uri").and_then(|h| h.to_str().ok())?.parse::().ok()?;
// Make HTTP request to potentially poisoned DNS
let res = reqwest::get(uri.to_string()).await;
(res.status(), res.headers(), res.bytes().await.unwrap())
}
let app = Router::new().route("/proxy", get(proxy_request)); This code is vulnerable because it trusts the x-forwarded-uri header without DNS validation. An attacker can poison the DNS cache to redirect requests to their controlled server, potentially exposing sensitive data or enabling further attacks.
Another Axum-specific vulnerability occurs in WebSocket connections where the DNS resolution happens once during connection establishment:
use axum::extract::ws::{WebSocket, WebSocketUpgrade};
use axum::http::HeaderValue;
use axum::routing::get;
use axum::Router;
async fn websocket_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.on_upgrade(|socket| async move {
// DNS resolution happens once here
// If poisoned, connection goes to malicious server
let (mut sender, mut receiver) = socket.split();
while let Some(msg) = receiver.next().await {
// Process messages from potentially malicious source
}
})
}
let app = Router::new().route("/ws", get(websocket_handler));The WebSocket connection establishes a single TCP connection based on DNS resolution. If the DNS cache is poisoned at connection time, all subsequent communication happens with the malicious server, bypassing any TLS verification that might occur later.
Axum-Specific Detection
Detecting DNS cache poisoning in Axum applications requires examining both the code patterns and runtime behavior. middleBrick's black-box scanning approach is particularly effective for this, as it tests the actual API endpoints without requiring source code access.
middleBrick scans for DNS-related vulnerabilities by examining how your Axum application handles external requests. The scanner tests for:
- Insecure DNS resolution patterns in HTTP client usage
- Missing timeout configurations that allow DNS poisoning to persist
- Unsafe handling of user-controlled URLs and host headers
- WebSocket connection establishment without DNS validation
The scanner specifically looks for Axum's common patterns like TypedHeader extractors that might accept malicious URLs, and WebSocketUpgrade handlers that establish connections based on potentially poisoned DNS.
For manual detection in Axum code, look for these patterns:
use axum::extract::Path;
use axum::http::Uri;
use axum::response::IntoResponse;
use axum::routing::get;
use axum::Router;
// Vulnerable pattern - no DNS validation
async fn user_profile(
Path(user_id): Path<u32>,
) -> impl IntoResponse {
let api_url = format!("https://api.example.com/users/{}", user_id);
// This request is vulnerable to DNS cache poisoning
let res = reqwest::get(api_url).await;
res.unwrap()
}
let app = Router::new().route("/user/:id", get(user_profile));middleBrick's scanning would flag this because it makes external HTTP requests without proper DNS validation or timeout controls. The scanner tests by attempting to resolve DNS entries and checking if the application properly handles DNS resolution failures or timeouts.
The scanner also examines middleware that might perform DNS lookups:
use axum::middleware::Next;
use axum::response::IntoResponse;
use axum::routing::get;
use axum::Router;
async fn auth_middleware(
mut req: axum::http::Request,
next: Next,
) -> impl IntoResponse {
// Vulnerable: DNS resolution without validation
let auth_url = "https://auth.example.com/validate".parse().unwrap();
let client = reqwest::Client::new();
let auth_res = client.get(auth_url).send().await;
if auth_res.status().is_success() {
next.run(req).await
} else {
(401, "Unauthorized")
}
}
let app = Router::new().route("/protected", get(protected_handler));
let app = app.layer(auth_middleware);middleBrick would detect this middleware pattern as high risk because it performs external authentication calls without DNS security controls, making it vulnerable to DNS cache poisoning attacks that could bypass authentication.
Axum-Specific Remediation
Remediating DNS cache poisoning in Axum requires implementing proper DNS validation, timeout controls, and secure HTTP client configurations. The key is to use Axum's extensibility to add security layers without breaking existing functionality.
First, implement DNS over HTTPS (DoH) or DNS over TLS (DoT) for all external requests:
use axum::extract::Path;
use axum::response::IntoResponse;
use axum::routing::get;
use axum::Router;
use reqwest::Client;
use std::time::Duration;
// Create a secure client with DNS over HTTPS
let secure_client = Client::builder()
.pool_idle_timeout(Duration::from_secs(30))
.pool_conn_timeout(Duration::from_secs(10))
.dns_overrides(reqwest::dns_overrides::cloudflare())
.build()
.unwrap();
async fn user_profile(
Path(user_id): Path<u32>,
client: Client,
) -> impl IntoResponse {
let api_url = format!("https://api.example.com/users/{}", user_id);
// Use secure client with DNS over HTTPS
let res = client
.get(api_url)
.timeout(Duration::from_secs(10))
.send()
.await;
match res {
Ok(response) => (response.status(), response.headers(), response.bytes().await.unwrap()),
Err(_) => (500, "Internal Server Error"),
}
}
let app = Router::new().route("/user/:id", get(user_profile));
let app = app.with_state(secure_client);This code uses Cloudflare's DoH resolver, which prevents cache poisoning by encrypting DNS queries and using a trusted resolver. The timeout controls ensure that poisoned DNS entries don't persist indefinitely.
For WebSocket connections, implement DNS validation before establishing connections:
use axum::extract::ws::{WebSocket, WebSocketUpgrade};
use axum::http::HeaderValue;
use axum::routing::get;
use axum::Router;
use tokio::net::lookup_host;
async fn secure_websocket_handler(
ws: WebSocketUpgrade,
target_host: String,
) -> impl IntoResponse {
// Validate DNS before upgrading
let resolved_ips = lookup_host(target_host).await;
if let Ok(ips) = resolved_ips {
if ips.len() > 0 {
// DNS resolved successfully, proceed with connection
ws.on_upgrade(|socket| async move {
// Connection established with validated DNS
let (mut sender, mut receiver) = socket.split();
while let Some(msg) = receiver.next().await {
// Process messages from verified source
}
})
} else {
(400, "Invalid target host")
}
} else {
(500, "DNS resolution failed")
}
}
let app = Router::new().route("/ws/:host", get(secure_websocket_handler));This approach validates DNS resolution before establishing WebSocket connections, preventing poisoned DNS from redirecting connections to malicious servers.
Implement middleware for comprehensive DNS security:
use axum::middleware::Next;
use axum::response::IntoResponse;
use axum::routing::get;
use axum::Router;
use reqwest::Client;
use std::time::Duration;
async fn dns_secure_middleware(
mut req: axum::http::Request,
next: Next,
client: Client,
) -> impl IntoResponse {
// Check if request contains user-controlled URLs
if let Some(host) = req.uri().host() {
// Validate host against allowlist or use secure DNS
if is_trusted_domain(host) {
return next.run(req).await;
}
}
// For external requests, use secure client
let secure_client = Client::builder()
.pool_idle_timeout(Duration::from_secs(30))
.pool_conn_timeout(Duration::from_secs(10))
.dns_overrides(reqwest::dns_overrides::cloudflare())
.build()
.unwrap();
// Replace client in request context
req.extensions_mut().insert(secure_client);
next.run(req).await
}
fn is_trusted_domain(host: &str) -> bool {
// Implement domain allowlist or use secure DNS resolution
vec!["trusted.com", "api.example.com"].contains(&host)
}
let app = Router::new().route("/api/*", get(api_handler));
let app = app.layer(dns_secure_middleware);This middleware layer ensures all requests use secure DNS resolution and validates hostnames against trusted domains, preventing DNS cache poisoning from affecting your Axum application.