Verlass auf Reverse-DNS-Auflösung für sicherheitskritische Aktionen

Beschreibung

Verlass auf Reverse-DNS-Auflösung für sicherheitskritische Aktionen tritt auf, wenn Software eine Sicherheitsentscheidung basierend auf dem Ergebnis einer Reverse-DNS-Abfrage (PTR-Eintrag) trifft. Reverse-DNS ist leicht fälschbar - Angreifer, die einen DNS-Server kontrollieren, können jeden Hostnamen so konfigurieren, dass er zu ihrer IP-Adresse auflöst. Dies ermöglicht Angreifern, IP-basierte Zugriffskontrollen zu umgehen, Log-Einträge zu fälschen oder vertrauenswürdige Systeme zu imitieren, indem sie Reverse-DNS-Einträge manipulieren.

Risiko

DNS ist fundamental eine nicht vertrauenswürdige Infrastruktur. Angreifer können PTR-Einträge einrichten, die jeden Hostnamen auf ihre IP-Adressen verweisen. Systeme, die sich für Authentifizierung oder Autorisierung auf Reverse-DNS verlassen, werden trivial umgangen. Selbst legitimes Reverse-DNS kann inkonsistent oder veraltet sein. Diese Schwachstelle wurde ausgenutzt, um Firewall-Regeln zu umgehen, Zugang zu eingeschränkten Diensten zu erhalten und Logs mit falscher Zuordnung zu vergiften. Viele Legacy-Systeme verwenden immer noch Hostname-basierte Vertrauensmodelle, die anfällig sind.

Lösung

Verwenden Sie niemals Reverse-DNS für Sicherheitsentscheidungen. Verwenden Sie IP-Adressen-basierte Allowlists direkt. Implementieren Sie ordnungsgemäße Authentifizierungsmechanismen (Zertifikate, API-Schlüssel, gegenseitiges TLS). Wenn Hostname-Logging benötigt wird, behandeln Sie es nur als informativ - loggen Sie sowohl IP als auch Hostname. Verwenden Sie Forward-Confirmed Reverse DNS (FCrDNS), wenn Hostname-Informationen erforderlich sind: verifizieren Sie die Reverse-Abfrage durch Durchführung einer Forward-Abfrage und bestätigen Sie, dass die IP übereinstimmt. Implementieren Sie zertifikatsbasierte Authentifizierung für Service-zu-Service-Kommunikation.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Authentifizierungs-Bypass

Angreifer können vertrauenswürdige Hostnamen fälschen, um Zugriffskontrollen zu umgehen.
IntegritätUmfang: Log-Fälschung

Angreifer können bösartige Aktionen so erscheinen lassen, als stammten sie von vertrauenswürdigen Systemen.
Nicht-AbstreitbarkeitUmfang: Falsche Zuordnung

Sicherheitslogs werden für forensische Analyse unzuverlässig.

Beispielcode + Korrigierter Code

Anfälliger Code

# ANFÄLLIG: Reverse-DNS für Zugriffskontrolle verwenden
import socket

TRUSTED_HOSTS = ['admin.internal.company.com', 'backup.internal.company.com']

def check_access_vulnerable(client_ip):
    try:
        # Reverse-DNS-Abfrage - leicht fälschbar!
        hostname = socket.gethostbyaddr(client_ip)[0]

        # Angreifer kann seinen PTR-Eintrag auf admin.internal.company.com setzen
        if hostname in TRUSTED_HOSTS:
            return True
        if hostname.endswith('.internal.company.com'):
            return True
    except socket.herror:
        pass

    return False

# ANFÄLLIG: Logging mit Reverse-DNS als Zuordnung
def log_request_vulnerable(client_ip, action):
    hostname = socket.gethostbyaddr(client_ip)[0]

    # Log kann mit gefälschtem Hostnamen manipuliert werden
    log_entry = f"{hostname} ({client_ip}) führte {action} aus"
    logger.info(log_entry)

# ANFÄLLIG: hosts.allow-Stil-Prüfung
def is_allowed_host_vulnerable(client_ip):
    hostname, _, _ = socket.gethostbyaddr(client_ip)

    # Musterabgleich auf nicht vertrauenswürdigem Hostnamen
    if hostname.endswith('.trusted-partner.com'):
        return True
    return False
// ANFÄLLIG: Java Reverse-DNS Zugriffskontrolle
import java.net.InetAddress;

public class VulnerableAccessControl {

    private static final Set<String> TRUSTED_DOMAINS = Set.of(
        "admin.internal.company.com",
        "backup.internal.company.com"
    );

    public boolean isAllowed(String clientIp) throws Exception {
        InetAddress addr = InetAddress.getByName(clientIp);

        // Reverse-DNS - angreifer-kontrolliert!
        String hostname = addr.getCanonicalHostName();

        // Vertrauen basierend auf Hostname - anfällig!
        if (TRUSTED_DOMAINS.contains(hostname)) {
            return true;
        }

        if (hostname.endsWith(".internal.company.com")) {
            return true;
        }

        return false;
    }

    // ANFÄLLIG: Servlet-Filter mit Hostname
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        String hostname = request.getRemoteHost();  // Reverse-DNS-Abfrage!

        if (!hostname.endsWith(".company.com")) {
            ((HttpServletResponse) response).sendError(403);
            return;
        }

        chain.doFilter(request, response);
    }
}
// ANFÄLLIG: Node.js Reverse-DNS-Prüfung
const dns = require('dns');

const TRUSTED_HOSTS = new Set([
    'admin.internal.company.com',
    'monitoring.internal.company.com'
]);

async function checkAccessVulnerable(clientIp) {
    return new Promise((resolve, reject) => {
        // Reverse-DNS - Angreifer kann fälschen!
        dns.reverse(clientIp, (err, hostnames) => {
            if (err) {
                resolve(false);
                return;
            }

            // Fälschbarem DNS vertrauen
            for (const hostname of hostnames) {
                if (TRUSTED_HOSTS.has(hostname)) {
                    resolve(true);
                    return;
                }
                if (hostname.endsWith('.internal.company.com')) {
                    resolve(true);
                    return;
                }
            }
            resolve(false);
        });
    });
}

// ANFÄLLIG: Express-Middleware
app.use((req, res, next) => {
    dns.reverse(req.ip, (err, hostnames) => {
        if (err || !hostnames.some(h => h.endsWith('.trusted.com'))) {
            return res.status(403).send('Zugriff verweigert');
        }
        next();
    });
});

Korrigierter Code

# SICHER: IP-basierte Zugriffskontrolle
import ipaddress
import socket

# Vertrauenswürdige IPs/Netzwerke direkt definieren
TRUSTED_IPS = {
    ipaddress.ip_address('10.0.1.5'),
    ipaddress.ip_address('10.0.1.6'),
}

TRUSTED_NETWORKS = [
    ipaddress.ip_network('10.0.0.0/8'),
    ipaddress.ip_network('192.168.1.0/24'),
]

def check_access_safe(client_ip):
    """Zugriff nur basierend auf IP-Adresse prüfen."""
    try:
        ip = ipaddress.ip_address(client_ip)

        # Spezifische IPs prüfen
        if ip in TRUSTED_IPS:
            return True

        # Netzwerkbereiche prüfen
        for network in TRUSTED_NETWORKS:
            if ip in network:
                return True

    except ValueError:
        pass  # Ungültige IP

    return False

# SICHER: Forward-Confirmed Reverse DNS (FCrDNS)
def get_verified_hostname(ip_address):
    """Hostname nur zurückgeben wenn Forward-Abfrage IP bestätigt."""
    try:
        # Schritt 1: Reverse-Abfrage
        hostname, _, _ = socket.gethostbyaddr(ip_address)

        # Schritt 2: Forward-Abfrage zur Verifizierung
        _, _, ip_list = socket.gethostbyname_ex(hostname)

        # Schritt 3: Bestätigen dass ursprüngliche IP in Forward-Ergebnissen ist
        if ip_address in ip_list:
            return hostname  # Verifiziert!
        else:
            return None  # Forward-Abfrage stimmt nicht überein

    except (socket.herror, socket.gaierror):
        return None

# SICHER: Logging mit informativem Hostnamen
def log_request_safe(client_ip, action):
    # Verifizierten Hostnamen holen falls verfügbar
    verified_hostname = get_verified_hostname(client_ip)

    # IP als maßgeblich loggen, Hostname als informativ
    if verified_hostname:
        log_entry = f"[{client_ip}] (verifiziert: {verified_hostname}) führte {action} aus"
    else:
        log_entry = f"[{client_ip}] führte {action} aus"

    logger.info(log_entry)

# SICHER: Gegenseitiges TLS für Service-Authentifizierung verwenden
from flask import Flask
import ssl

app = Flask(__name__)

# Client-Zertifikat-Authentifizierung
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain('server.crt', 'server.key')
ssl_context.load_verify_locations('trusted_ca.crt')
ssl_context.verify_mode = ssl.CERT_REQUIRED  # Client-Zertifikat erforderlich

@app.before_request
def verify_client_cert():
    # Client-Zertifikat-Info holen
    client_cert = request.environ.get('SSL_CLIENT_CERT')
    if not client_cert:
        abort(403, 'Client-Zertifikat erforderlich')

    # Gegen bekannte Clients verifizieren
    cert_cn = extract_cn_from_cert(client_cert)
    if cert_cn not in TRUSTED_CLIENTS:
        abort(403, 'Unbekannter Client')
// SICHER: Java IP-basierte Zugriffskontrolle
import java.net.InetAddress;
import java.util.Set;

public class SecureAccessControl {

    // IP-basierte Allowlist
    private static final Set<String> TRUSTED_IPS = Set.of(
        "10.0.1.5",
        "10.0.1.6",
        "192.168.1.100"
    );

    // CIDR-basierte Allowlist
    private static final List<IpRange> TRUSTED_NETWORKS = List.of(
        new IpRange("10.0.0.0/8"),
        new IpRange("192.168.0.0/16")
    );

    public boolean isAllowed(String clientIp) {
        // Spezifische IPs prüfen
        if (TRUSTED_IPS.contains(clientIp)) {
            return true;
        }

        // Netzwerkbereiche prüfen
        for (IpRange range : TRUSTED_NETWORKS) {
            if (range.contains(clientIp)) {
                return true;
            }
        }

        return false;
    }

    // SICHER: FCrDNS-Verifizierung
    public String getVerifiedHostname(String ipAddress) {
        try {
            InetAddress addr = InetAddress.getByName(ipAddress);

            // Reverse-Abfrage
            String hostname = addr.getCanonicalHostName();

            // Wenn kein Reverse, gibt IP-String zurück
            if (hostname.equals(ipAddress)) {
                return null;
            }

            // Forward-Abfrage-Verifizierung
            InetAddress[] forwardAddrs = InetAddress.getAllByName(hostname);

            for (InetAddress fwd : forwardAddrs) {
                if (fwd.getHostAddress().equals(ipAddress)) {
                    return hostname;  // Verifiziert!
                }
            }

            return null;  // Forward bestätigt Reverse nicht

        } catch (Exception e) {
            return null;
        }
    }
}

// SICHER: Servlet-Filter mit IP-basierter Kontrolle
@WebFilter("/*")
public class SecureAccessFilter implements Filter {

    private final SecureAccessControl accessControl = new SecureAccessControl();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        String clientIp = request.getRemoteAddr();

        // IP verwenden, nicht Hostname!
        if (!accessControl.isAllowed(clientIp)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
            return;
        }

        chain.doFilter(request, response);
    }
}

// SICHER: Client-Zertifikate verwenden
@Configuration
public class MutualTLSConfig {

    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();

        factory.addConnectorCustomizers(connector -> {
            connector.setScheme("https");
            connector.setSecure(true);

            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
            protocol.setSSLEnabled(true);
            protocol.setClientAuth("true");  // Client-Zertifikat erforderlich
            protocol.setKeystoreFile("server.jks");
            protocol.setTruststoreFile("trusted-clients.jks");
        });

        return factory;
    }
}
// SICHER: Node.js IP-basierte Zugriffskontrolle
const ipRangeCheck = require('ip-range-check');

const TRUSTED_IPS = new Set([
    '10.0.1.5',
    '10.0.1.6',
    '192.168.1.100'
]);

const TRUSTED_RANGES = [
    '10.0.0.0/8',
    '192.168.0.0/16',
    '172.16.0.0/12'
];

function checkAccess(clientIp) {
    // Spezifische IPs prüfen
    if (TRUSTED_IPS.has(clientIp)) {
        return true;
    }

    // CIDR-Bereiche prüfen
    if (ipRangeCheck(clientIp, TRUSTED_RANGES)) {
        return true;
    }

    return false;
}

// SICHER: Express-Middleware mit IP-Prüfung
app.use((req, res, next) => {
    const clientIp = req.ip || req.connection.remoteAddress;

    if (!checkAccess(clientIp)) {
        return res.status(403).send('Zugriff verweigert');
    }

    next();
});

// SICHER: FCrDNS-Verifizierung
const dns = require('dns').promises;

async function getVerifiedHostname(ipAddress) {
    try {
        // Reverse-Abfrage
        const hostnames = await dns.reverse(ipAddress);
        if (!hostnames.length) return null;

        const hostname = hostnames[0];

        // Forward-Abfrage-Verifizierung
        const addresses = await dns.resolve(hostname);

        if (addresses.includes(ipAddress)) {
            return hostname;  // Verifiziert!
        }

        return null;  // Nicht bestätigt

    } catch (error) {
        return null;
    }
}

// SICHER: Gegenseitige TLS-Authentifizierung
const https = require('https');
const fs = require('fs');

const options = {
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem'),
    ca: fs.readFileSync('trusted-ca.pem'),
    requestCert: true,
    rejectUnauthorized: true  // Gültiges Client-Zertifikat erforderlich
};

const server = https.createServer(options, (req, res) => {
    // Client-Zertifikat wird durch TLS-Handshake verifiziert
    const clientCert = req.socket.getPeerCertificate();

    if (!clientCert.subject) {
        res.writeHead(403);
        res.end('Client-Zertifikat erforderlich');
        return;
    }

    // Zertifikats-CN zur Identifizierung verwenden
    const clientId = clientCert.subject.CN;
    console.log(`Authentifizierter Client: ${clientId}`);

    // Anfrage verarbeiten...
});

Ausgenutzt in der Praxis

Historische Unix r-Befehle

Die Befehle rlogin, rsh und rexec verwendeten .rhosts-Dateien für hostname-basiertes Vertrauen, was zu weitreichenden Netzwerkkompromittierungen durch DNS-Spoofing führte.

IP-basierte Zugriffskontrolle-Bypass

Zahlreiche Systeme wurden kompromittiert, indem Angreifer PTR-Einträge für ihre IP-Adressen einrichteten, die mit vertrauenswürdigen internen Hostnamen übereinstimmten.

Log-Injektions-Angriffe

Angreifer haben Reverse-DNS-Spoofing verwendet, um irreführende Informationen in Sicherheitslogs zu injizieren, was Incident Response erschwert.


Tools zum Testen/Ausnutzen

  • nslookup — Manuelles DNS-Testen.

  • dig — DNS-Abfragewerkzeug.

  • DNS-Server-Konfiguration — Benutzerdefinierte PTR-Einträge zum Testen setzen.

  • Wireshark — DNS-Verkehr analysieren.


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-350: Reliance on Reverse DNS Resolution for a Security-Critical Action." https://cwe.mitre.org/data/definitions/350.html

  2. CERT. "DNS Spoofing." https://www.us-cert.gov/ncas/alerts