Dns Cache Poisoning in Actix (Rust)
Rust-Specific Remediation in Actix
To mitigate DNS cache poisoning risks in Actix applications, implement defense-in-depth strategies at the application layer, since OS-level DNS hardening is outside the developer’s direct control. The following Rust code examples demonstrate concrete mitigations using idiomatic Actix and reqwest patterns.
First, enforce strict TLS validation and certificate pinning for outbound requests. Never disable certificate checks, and validate the server’s identity beyond hostname matching. Use reqwest with a custom ClientBuilder to enforce certificate pinning:
use reqwest::{Client, Certificate};
use std::fs;
async fn create_secure_client() -> Result {
// Load the expected certificate (e.g., from a trusted CA or service cert)
let cert = Certificate::from_pem(&fs::read("trusted-ca.pem")?)
.map_err(|e| reqwest::Error::new(reqwest::ErrorKind::Other, e))?;
Client::builder()
.add_root_certificate(cert)
.danger_accept_invalid_certs(false) // Explicitly reject invalid certs
.build()
}
#[actix_web::get("/profile")]
async fn fetch_profile() -> actix_web::Result {
let client = create_secure_client().await?;
let res = client
.get("https://api.internal-service.com/user/profile")
.send()
.await?
.text()
.await?;
Ok(res)
}
Second, implement IP address allowlisting after DNS resolution. Resolve the hostname early and validate the resolved IP against a predefined list or range. This prevents reliance on potentially poisoned DNS results at request time:
use std::net::{IpAddr, Ipv4Addr}; use tokio::net::lookup_host; async fn resolve_and_validate(host: &str) -> Result> { let mut allowed_ips = vec![ Ipv4Addr::new(10, 0, 0, 5).into(), Ipv4Addr::new(10, 0, 0, 6).into(), ]; let mut resolved = lookup_host(host).await?; if let Some(ip) = resolved.next() { let ip_addr = ip.ip(); if allowed_ips.contains(&ip_addr) { Ok(ip_addr) } else { Err("Resolved IP not in allowlist".into()) } } else { Err("No IP addresses resolved".into()) } } #[actix_web::get("/data")] async fn fetch_data() -> actix_web::Result { let ip = resolve_and_validate("api.internal-service.com").await?; let url = format!("https://{}/data", ip); let client = reqwest::Client::builder() .danger_accept_invalid_certs(false) .build()?; let res = client.get(&url).send().await?.text().await?; Ok(res) } Third, consider using DNS-over-HTTPS (DoH) or DNS-over-TLS (DoT) via trusted resolvers (e.g., Cloudflare, Google) to bypass local DNS vulnerabilities. While Actix doesn’t include a DoH client, libraries like
trust-dns-resolvercan be integrated:use trust_dns_resolver::{config::*, resolver::*}; use trust_dns_resolver::system_conf::read_system_conf; async fn create_doh_resolver() -> Result> { let mut config = ResolverConfig::default(); config.add_udp_nameserver( ("1.1.1.1", 443), Protocol::Https, "cloudflare-dns.com" ); let mut opts = ResolverOpts::default(); opts.validate = true; // Enable DNSSEC validation Ok(Resolver::new(config, opts)?) } #[actix_web::get("/secure")] async fn secure_endpoint() -> actix_web::Result { let resolver = create_doh_resolver().await?; let response = resolver.lookup_ip("api.payment.com").await?; // Use first resolved IP (or iterate with validation) if let Some(ip) = response.iter().next() { // Proceed with IP validation or direct connection Ok(format!("Resolved to: {}", ip)) } else { Err("DNS lookup failed".into()) } } These measures ensure that even if the local DNS cache is poisoned, the Actix application validates the integrity of outbound connections through cryptographic TLS checks, IP allowlisting, or encrypted DNS resolution. Combine them with network segmentation and egress filtering for layered defense.