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
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: Authentifizierungs-Bypass Angreifer können vertrauenswürdige Hostnamen fälschen, um Zugriffskontrollen zu umgehen. |
| Integrität | Umfang: Log-Fälschung Angreifer können bösartige Aktionen so erscheinen lassen, als stammten sie von vertrauenswürdigen Systemen. |
| Nicht-Abstreitbarkeit | Umfang: 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
-
CVE-2019-3462 — APT DNS-basierter Angriffsvektor.
-
CVE-2017-3144 — ISC DHCP Reverse-DNS-Problem.
-
CVE-2015-7547 — glibc DNS-Auflösungs-Schwachstelle.
Referenzen
-
MITRE. "CWE-350: Reliance on Reverse DNS Resolution for a Security-Critical Action." https://cwe.mitre.org/data/definitions/350.html
-
CERT. "DNS Spoofing." https://www.us-cert.gov/ncas/alerts