HIGH dns cache poisoningactix

Dns Cache Poisoning in Actix

How DNS Cache Poisoning Manifests in Actix

DNS cache poisoning tricks a resolver into storing a fraudulent mapping between a domain name and an IP address. In an Actix‑based service the risk appears when the application performs outbound HTTP requests based on user‑supplied hostnames without validating the resolved address. A typical vulnerable pattern is a handler that forwards a request to an external API whose endpoint is built from a query parameter.

use actix_web::{web, HttpResponse, Responder};
use reqwest::Client;

async fn proxy(host: web::Path) -> impl Responder {
    let url = format!("http://{}/data", host.into_inner());
    let resp = Client::new().get(&url).send().await.ok()?;
    let body = resp.text().await.ok()?;
    HttpResponse::Ok().body(body)
}

If an attacker can poison the DNS cache of the Actix server’s resolver (e.g., via a Kaminsky‑style attack exploiting predictable transaction IDs), the resolver may return an internal IP address for evil.com. The handler above will then send a request to that internal address, achieving SSRF or enabling further pivoting inside the network.

Actix‑specific code paths that increase exposure include:

  • Direct use of reqwest::Client or actix‑web::client::Client with a hostname derived from request data.
  • Custom middleware that uses actix‑net::Resolver to resolve hostnames before load‑balancing or service‑discovery.
  • Plugins that perform reverse‑DNS lookups for logging or access‑control without validating the result.

Because Actix does not enforce DNSSEC or source‑port randomization at the application layer, the application inherits whatever resolver configuration the host OS provides, making it susceptible to classic cache‑poisoning vectors when those OS settings are weak.

Actix‑Specific Detection

Detecting DNS cache‑poisoning risk in an Actix service starts with reviewing code that builds outbound URLs from untrusted input. Look for:

  • Handlers that accept a hostname, subdomain, or path segment and directly concatenate it into a URL string.
  • Use of actix‑web::client or reqwest without an intermediate validation step.
  • Calls to actix‑net::Resolver::lookup_ip or similar APIs where the result is used without checking whether the resolved IP falls inside private ranges (RFC 1918, RFC 4193, RFC 5737).

middleBrick can help surface these issues automatically. Its Input Validation check probes the unauthenticated attack surface by sending crafted hostnames (e.g., attacker-controlled.internal) and monitoring for outbound connections to unexpected IP ranges. When such behavior is observed, middleBrick reports a finding with severity, the exact endpoint tested, and remediation guidance.

Example CLI usage:

middlebrick scan https://api.example.com/proxy

The command returns a JSON report that includes a section like:

{
  "category": "Input Validation",
  "finding": "Potential DNS cache poisoning via user‑supplied hostname",
  "severity": "medium",
  "remediation": "Validate or allow‑list hostnames before performing outbound DNS lookups."
}

In addition to automated scanning, developers can write unit tests that mock the resolver and assert that the handler rejects private IP addresses or non‑allowed domains.

Actix‑Specific Remediation

The most reliable defense is to eliminate reliance on DNS for security decisions. Actix provides native tools to validate hostnames and IP addresses before making outbound requests.

1. **Allow‑list validation** – maintain a list of trusted domains or IP ranges and reject anything else.

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use trust_dns_resolver::AsyncResolver;

#[derive(Clone)]
service struct SafeProxy {
    allowed: Vec,   // e.g. vec!["api.github.com".to_string()]
    resolver: AsyncResolver,
}

impl SafeProxy {
    fn new() -> Self {
        let resolver = AsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()).unwrap();
        Self { allowed: vec!["api.trusted.com".to_string()], resolver }
    }

    async fn call(&self, host: &str) -> Result {
        // 1️⃣ Ensure host is in allow‑list
        if !self.allowed.contains(&host.to_string()) {
            return Err(reqwest::Error::new(reqwest::ErrorKind::InvalidInput, "host not allowed"));
        }
        // 2️⃣ Resolve with DNSSEC‑enabled resolver
        let lookup = self.resolver.lookup_ip(host).await?;
        for ip in lookup.iter() {
            // 3️⃣ Reject private / loopback addresses
            if ip.is_private() || ip.is_loopback() {
                return Err(reqwest::Error::new(reqwest::ErrorKind::InvalidInput, "resolved to private IP"));
            }
        }
        let url = format!("https://{}", host);
        reqwest::get(&url).await?.text().await
    }
}

2. **Use a secure resolver** – the trust‑dns-resolver crate supports DNSSEC validation. By constructing an AsyncResolver with DNSSEC enabled, the application discards responses that fail validation, mitigating classic cache‑poisoning attempts.

3. **Leverage Actix‑web’s built‑in client with a custom connector** – actix‑web allows plugging in a Connector that performs the above checks before opening a socket.

use actix_web::client::{Connector, Client};
use std::net::IpAddr;

let connector = Connector::new()
    .timeout(std::time::Duration::from_secs(5))
    .finish();
let client = Client::builder()
    .connector(connector)
    .finish();

// In a handler:
let resp = client.get("http://user‑supplied-host.com/data")
    .send()
    .await?;

By combining an allow‑list, DNSSEC‑validating resolver, and IP‑range checks, an Actix application removes the dependency on potentially poisoned DNS cache and eliminates the SSRF vector that DNS cache poisoning would otherwise enable.

Frequently Asked Questions

Can middleBrick directly detect a DNS cache‑poisoning attack on my Actix service?
middleBrick does not run a live DNS poisoning attack; instead, its Input Validation check sends crafted hostnames and watches for outbound connections to unexpected IP ranges. If the service resolves a supplied hostname to an internal or private address, middleBrick flags this as a potential DNS cache‑poisoning risk and provides remediation guidance.
What is the simplest first step to protect my Actix proxy from DNS‑based SSRF?
Start by validating the hostname against an explicit allow‑list before performing any DNS lookup. Reject any host not on the list, and after resolution, verify that the returned IP address is not private or loopback. This can be done with a few lines of Rust using the standard library’s IpAddr::is_private() method.