Reliance on Reverse DNS Resolution for a Security-Critical Action
Description
Reliance on Reverse DNS Resolution for a Security-Critical Action occurs when software performs a security decision based on the result of a reverse DNS lookup (PTR record). Reverse DNS is easily spoofable—attackers who control a DNS server can configure any hostname to resolve to their IP address. This allows attackers to bypass IP-based access controls, forge log entries, or impersonate trusted systems by manipulating reverse DNS records.
Risk
DNS is fundamentally untrusted infrastructure. Attackers can set up PTR records pointing any hostname to their IP addresses. Systems relying on reverse DNS for authentication or authorization are trivially bypassed. Even legitimate reverse DNS can be inconsistent or stale. This vulnerability has been exploited to bypass firewall rules, gain access to restricted services, and poison logs with false attribution. Many legacy systems still use hostname-based trust models that are vulnerable.
Solution
Never use reverse DNS for security decisions. Use IP address-based allowlists directly. Implement proper authentication mechanisms (certificates, API keys, mutual TLS). If hostname logging is needed, treat it as informational only—log both IP and hostname. Use forward-confirmed reverse DNS (FCrDNS) if hostname information is required: verify the reverse lookup by performing a forward lookup and confirming the IP matches. Implement certificate-based authentication for service-to-service communication.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Authentication Bypass Attackers can spoof trusted hostnames to bypass access controls. |
| Integrity | Scope: Log Forgery Attackers can make malicious actions appear to originate from trusted systems. |
| Non-Repudiation | Scope: False Attribution Security logs become unreliable for forensic analysis. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: Using reverse DNS for access control
import socket
TRUSTED_HOSTS = ['admin.internal.company.com', 'backup.internal.company.com']
def check_access_vulnerable(client_ip):
try:
# Reverse DNS lookup - easily spoofable!
hostname = socket.gethostbyaddr(client_ip)[0]
# Attacker can set their PTR record to admin.internal.company.com
if hostname in TRUSTED_HOSTS:
return True
if hostname.endswith('.internal.company.com'):
return True
except socket.herror:
pass
return False
# VULNERABLE: Logging with reverse DNS as attribution
def log_request_vulnerable(client_ip, action):
hostname = socket.gethostbyaddr(client_ip)[0]
# Log can be manipulated with fake hostname
log_entry = f"{hostname} ({client_ip}) performed {action}"
logger.info(log_entry)
# VULNERABLE: hosts.allow style checking
def is_allowed_host_vulnerable(client_ip):
hostname, _, _ = socket.gethostbyaddr(client_ip)
# Pattern matching on untrusted hostname
if hostname.endswith('.trusted-partner.com'):
return True
return False
// VULNERABLE: Java reverse DNS access control
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 - attacker controlled!
String hostname = addr.getCanonicalHostName();
// Trust based on hostname - vulnerable!
if (TRUSTED_DOMAINS.contains(hostname)) {
return true;
}
if (hostname.endsWith(".internal.company.com")) {
return true;
}
return false;
}
// VULNERABLE: Servlet filter using hostname
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String hostname = request.getRemoteHost(); // Reverse DNS lookup!
if (!hostname.endsWith(".company.com")) {
((HttpServletResponse) response).sendError(403);
return;
}
chain.doFilter(request, response);
}
}
// VULNERABLE: Node.js reverse DNS checking
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 - attacker can spoof!
dns.reverse(clientIp, (err, hostnames) => {
if (err) {
resolve(false);
return;
}
// Trusting spoofable DNS
for (const hostname of hostnames) {
if (TRUSTED_HOSTS.has(hostname)) {
resolve(true);
return;
}
if (hostname.endsWith('.internal.company.com')) {
resolve(true);
return;
}
}
resolve(false);
});
});
}
// VULNERABLE: 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('Access denied');
}
next();
});
});
Fixed Code
# SAFE: IP-based access control
import ipaddress
import socket
# Define trusted IPs/networks directly
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):
"""Check access based on IP address only."""
try:
ip = ipaddress.ip_address(client_ip)
# Check specific IPs
if ip in TRUSTED_IPS:
return True
# Check network ranges
for network in TRUSTED_NETWORKS:
if ip in network:
return True
except ValueError:
pass # Invalid IP
return False
# SAFE: Forward-Confirmed Reverse DNS (FCrDNS)
def get_verified_hostname(ip_address):
"""Get hostname only if forward lookup confirms IP."""
try:
# Step 1: Reverse lookup
hostname, _, _ = socket.gethostbyaddr(ip_address)
# Step 2: Forward lookup to verify
_, _, ip_list = socket.gethostbyname_ex(hostname)
# Step 3: Confirm original IP is in forward results
if ip_address in ip_list:
return hostname # Verified!
else:
return None # Forward lookup doesn't match
except (socket.herror, socket.gaierror):
return None
# SAFE: Logging with informational hostname
def log_request_safe(client_ip, action):
# Get verified hostname if available
verified_hostname = get_verified_hostname(client_ip)
# Log IP as authoritative, hostname as informational
if verified_hostname:
log_entry = f"[{client_ip}] (verified: {verified_hostname}) performed {action}"
else:
log_entry = f"[{client_ip}] performed {action}"
logger.info(log_entry)
# SAFE: Using mutual TLS for service authentication
from flask import Flask
import ssl
app = Flask(__name__)
# Client certificate authentication
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 # Require client cert
@app.before_request
def verify_client_cert():
# Get client certificate info
client_cert = request.environ.get('SSL_CLIENT_CERT')
if not client_cert:
abort(403, 'Client certificate required')
# Verify against known clients
cert_cn = extract_cn_from_cert(client_cert)
if cert_cn not in TRUSTED_CLIENTS:
abort(403, 'Unknown client')
// SAFE: Java IP-based access control
import java.net.InetAddress;
import java.util.Set;
public class SecureAccessControl {
// IP-based allowlist
private static final Set<String> TRUSTED_IPS = Set.of(
"10.0.1.5",
"10.0.1.6",
"192.168.1.100"
);
// CIDR-based 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) {
// Check specific IPs
if (TRUSTED_IPS.contains(clientIp)) {
return true;
}
// Check network ranges
for (IpRange range : TRUSTED_NETWORKS) {
if (range.contains(clientIp)) {
return true;
}
}
return false;
}
// SAFE: FCrDNS verification
public String getVerifiedHostname(String ipAddress) {
try {
InetAddress addr = InetAddress.getByName(ipAddress);
// Reverse lookup
String hostname = addr.getCanonicalHostName();
// If no reverse, returns IP string
if (hostname.equals(ipAddress)) {
return null;
}
// Forward lookup verification
InetAddress[] forwardAddrs = InetAddress.getAllByName(hostname);
for (InetAddress fwd : forwardAddrs) {
if (fwd.getHostAddress().equals(ipAddress)) {
return hostname; // Verified!
}
}
return null; // Forward doesn't confirm reverse
} catch (Exception e) {
return null;
}
}
}
// SAFE: Servlet filter with IP-based control
@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();
// Use IP, not hostname!
if (!accessControl.isAllowed(clientIp)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
chain.doFilter(request, response);
}
}
// SAFE: Using client certificates
@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"); // Require client cert
protocol.setKeystoreFile("server.jks");
protocol.setTruststoreFile("trusted-clients.jks");
});
return factory;
}
}
// SAFE: Node.js IP-based access control
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) {
// Check specific IPs
if (TRUSTED_IPS.has(clientIp)) {
return true;
}
// Check CIDR ranges
if (ipRangeCheck(clientIp, TRUSTED_RANGES)) {
return true;
}
return false;
}
// SAFE: Express middleware with IP check
app.use((req, res, next) => {
const clientIp = req.ip || req.connection.remoteAddress;
if (!checkAccess(clientIp)) {
return res.status(403).send('Access denied');
}
next();
});
// SAFE: FCrDNS verification
const dns = require('dns').promises;
async function getVerifiedHostname(ipAddress) {
try {
// Reverse lookup
const hostnames = await dns.reverse(ipAddress);
if (!hostnames.length) return null;
const hostname = hostnames[0];
// Forward lookup verification
const addresses = await dns.resolve(hostname);
if (addresses.includes(ipAddress)) {
return hostname; // Verified!
}
return null; // Not confirmed
} catch (error) {
return null;
}
}
// SAFE: Mutual TLS authentication
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 // Require valid client cert
};
const server = https.createServer(options, (req, res) => {
// Client certificate is verified by TLS handshake
const clientCert = req.socket.getPeerCertificate();
if (!clientCert.subject) {
res.writeHead(403);
res.end('Client certificate required');
return;
}
// Use certificate CN for identification
const clientId = clientCert.subject.CN;
console.log(`Authenticated client: ${clientId}`);
// Process request...
});
Exploited in the Wild
Historical Unix r-commands
The rlogin, rsh, and rexec commands used .rhosts files for hostname-based trust, leading to widespread network compromises through DNS spoofing.
IP-based Access Control Bypass
Numerous systems have been compromised by attackers setting up PTR records for their IP addresses to match trusted internal hostnames.
Log Injection Attacks
Attackers have used reverse DNS spoofing to inject misleading information into security logs, complicating incident response.
Tools to test/exploit
-
nslookup — manual DNS testing.
-
dig — DNS query tool.
-
DNS server configuration — set custom PTR records for testing.
-
Wireshark — analyze DNS traffic.
CVE Examples
-
CVE-2019-3462 — APT DNS-based attack vector.
-
CVE-2017-3144 — ISC DHCP reverse DNS issue.
-
CVE-2015-7547 — glibc DNS resolution vulnerability.
References
-
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