HIGH dangling dnsactixmutual tls

Dangling Dns in Actix with Mutual Tls

Dangling Dns in Actix with Mutual Tls — how this specific combination creates or exposes the vulnerability

A dangling DNS record occurs when a hostname resolves to an infrastructure that is no longer in use or no longer intended to serve the application. In an Actix web service configured for mutual TLS (mTLS), a dangling DNS entry can expose private endpoints or internal services if a client resolves the name to an unexpected IP. Because mTLS requires both the server and the client to present valid certificates, the mismatch between the expected DNS name and the actual host running the service can lead to failed handshakes or, in misconfigured deployments, to acceptance of connections that should not be trusted.

Consider a scenario where a staging environment is decommissioned but its DNS record is not removed or updated. If an Actix server is still reachable at that hostname and still requests client certificates, an attacker who can route traffic to that address might present a valid certificate issued for a different service. The Actix application, validating the client certificate but not strictly verifying that the hostname matches the certificate’s subject or SAN (subject alternative names), may accept the connection. This is especially risky when combined with internal IPs or when the mTLS configuration prioritizes certificate validity over hostname alignment.

The interaction between dangling DNS and mTLS in Actix becomes critical when certificate verification does not include strict hostname checks. For example, if the server-side TLS configuration uses a permissive domain pattern or does not enforce server name indication (SNI) validation, a client with a valid certificate for another service can connect to the dangling endpoint. The server may interpret the request as legitimate, potentially exposing routes that rely on mTLS for access control. This can lead to unauthorized access across microservices that share a mesh but do not expect cross-environment communication.

To detect this class of issue, scans should resolve the hostname independently and compare the resolved IPs to the expected deployment inventory. If the IP does not match known infrastructure, the DNS record is considered dangling. In the context of mTLS, scanners also validate that the server requests and verifies client certificates and that the hostname is checked against the certificate. middleBrick performs unauthenticated scans that include DNS resolution and TLS handshake analysis, checking for missing hostname verification in mTLS setups and identifying endpoints that may be exposed due to stale DNS entries.

An example of unsafe server-side configuration in Actix where hostname verification is omitted:

use actix_web::{web, App, HttpServer, Responder};
use actix_web::middleware::Logger;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};

fn create_ssl() -> SslAcceptor {
    let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
    builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
    builder.set_certificate_chain_file("cert.pem").unwrap();
    // Missing: set_verify_peer, set_verify_callback to enforce client cert hostname checks
    builder
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            .service(web::resource("/api/data").to(|| async { "ok" }))
    })
    .bind_openssl("0.0.0.0:8443", create_ssl())?
    .run()
    .await
}

Mutual Tls-Specific Remediation in Actix — concrete code fixes

To mitigate dangling DNS risks in an Actix service with mTLS, enforce strict hostname verification on the server side and ensure client certificates are issued only for intended DNS names. The server must validate that the client certificate’s subject or SAN matches the expected hostname. In OpenSSL terms, this means enabling peer verification and setting a proper verification callback that checks the hostname against the certificate.

Below is a secure Actix configuration that enables mTLS with hostname verification. The example loads server and CA certificates, requests and verifies client certificates, and checks the hostname using the certificate’s SAN or common name.

use actix_web::{web, App, HttpServer, Responder};
use actix_web::middleware::Logger;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslVerifyMode};
use openssl::x509::X509;
use std::net::IpAddr;

fn create_ssl() -> SslAcceptor {
    let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
    builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
    builder.set_certificate_chain_file("cert.pem").unwrap();
    // Require client certificate
    builder.set_verify(
        SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT,
        verify_callback,
    );
    builder
}

fn verify_callback(
    certs: &[X509],
    server_verifier: &mut openssl::ssl::ServerVerifyArgs,
) -> bool {
    // Expect at least one certificate
    let client_cert = match certs.first() {
        Some(cert) => cert,
        None => return false,
    };
    // Perform hostname verification: compare certificate SANs and CN with expected hostname
    let hostname = server_verifier.hostname().unwrap_or_default();
    if !verify_hostname(client_cert, hostname) {
        return false;
    }
    // Optionally, enforce additional constraints (e.g., extended key usage)
    true
}

fn verify_hostname(cert: &X509, hostname: &str) -> bool {
    // Check SANs first
    for i in 0..cert.subject_name().entry_count() {
        if let Some(entry) = cert.subject_name().get_entry_by_nid(entry.nid()) {
            let data = entry.data();
            if let Ok(s) = data.as_utf8_string() {
                // naive match; in production, use a proper hostname matching library
                if s == hostname {
                    return true;
                }
            }
        }
    }
    false
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            .service(web::resource("/api/data").to(|| async { "ok" }))
    })
    .bind_openssl("0.0.0.0:8443", create_ssl())?
    .run()
    .await
}

Key points in the remediation:

  • Set SslVerifyMode::PEER and FAIL_IF_NO_PEER_CERT to require client certificates.
  • Implement a verification callback that checks the client certificate against the hostname the client intended to reach (using SNI). This prevents a valid certificate issued for another service from being accepted against a dangling DNS name.
  • Ensure the hostname verification logic covers Subject Alternative Names and, if needed, the Common Name field, using a robust matching approach rather than simple string equality.

Additionally, remove or update stale DNS records so that the hostname does not resolve to unintended infrastructure. In CI/CD, the GitHub Action can be configured to fail builds if scans detect missing hostname verification in mTLS configurations, preventing deployments with such issues.

Frequently Asked Questions

How does middleBrick detect dangling DNS in combination with mTLS?
middleBrick resolves the endpoint hostname independently and compares the resolved IPs to known infrastructure. During the TLS handshake, it checks whether the server requests client certificates and whether hostname verification is performed, flagging missing hostname checks as a finding.
Can the GitHub Action prevent deployments with mTLS hostname verification issues?
Yes. The GitHub Action can be configured with a threshold; if a scan detects missing hostname verification in mTLS or other high-severity findings, it can fail the build and block deployment until the issue is addressed.