Dangerous Hostname Check
ID |
swift.dangerous_hostname_check |
Severity |
high |
Resource |
Risky Values |
Language |
Swift |
Tags |
CWE:350, NIST.SP.800-53, OWASP:2021:A04, PCI-DSS:6.5.8 |
Description
Dangerous checks against client-side hostname occur when an application resolves hostnames in a way that does not adequately enforce security policies or proper validation checks. This could result in DNS rebinding attacks or connections to unintended hosts, introducing potential risks like data interception or unauthorized access.
Rationale
Improper validation of hostnames can lead to significant security risks. If an application resolves a hostname that an attacker controls or can manipulate, it may connect to a malicious server instead of a legitimate one.
Attackers could exploit this by implementing DNS rebinding attacks or serving malicious content under a trusted hostname. Consequently, it’s vital to validate and verify hostnames to ensure the expected communication endpoint and data integrity.
In Swift applications, this flaw commonly occurs when using:
-
Vapor framework:
request.remoteAddress?.ipAddressfor reverse DNS -
POSIX functions:
getnameinfo()for low-level reverse DNS lookups -
Foundation (macOS):
NSHost(address:)for reverse DNS -
CFNetwork (deprecated):
CFHostCreateWithAddress()andCFHostGetNames() -
HTTP headers:
X-Forwarded-For,X-Real-IPfrom proxies
The following example shows using NSHost (macOS only) to resolve hostname using reverse DNS, and then perform a
import Foundation
func checkHostname(ipAddress: String) -> Bool {
// NSHost reverse DNS from IP address
let host = NSHost(address: ipAddress)
guard let hostname = host.name else { // FLAW
return false
}
// Security check based on reverse DNS
if hostname.hasSuffix(".trustme.com") {
return true
}
return false
}
An attacker can exploit this vulnerability through DNS rebinding or DNS spoofing:
-
Initial Setup: Attacker controls DNS server for
evil.trustme.com -
First Resolution: DNS returns attacker’s IP (e.g., 192.0.2.100)
-
Reverse DNS: Attacker configures reverse DNS for 192.0.2.100 →
evil.trustme.com -
Application Check: Application performs reverse DNS, gets
evil.trustme.com -
Bypass: Application checks
.hasSuffix(".trustme.com"), which passes. -
Exploitation: Attacker gains unauthorized access
Alternatively, through DNS cache poisoning, an attacker can poison the DNS cache to make legitimate IPs resolve to attacker-controlled hostnames.
Remediation
To remediate this vulnerability, the application should enforce hostname validation policies and include fallback mechanisms for unexpected resolutions. Follow these steps to mitigate risks associated with dangerous hostname resolution:
-
Whitelist Hostnames: Maintain a list of allowed hostnames. Before establishing a connection, verify that the resolved hostname or IP address is part of an approved list.
-
Reverse Lookup Verification: Conduct reverse DNS lookup to ensure the IP address maps back to the expected hostname.
-
Use of Secure DNS: Leverage DNS over HTTPS (DoH) or DNSSEC to ensure the integrity and authenticity of hostname resolutions.
-
Implement Custom Hostname Validators: Develop custom logic to ensure that hostnames conform to expected formats or resolve to trusted IP addresses.
-
Option 1: Whitelist IP Addresses (Recommended)
import Vapor
struct SecurityConfig {
static let allowedIPs: Set<String> = [
"192.168.1.10",
"10.0.0.50",
"172.16.0.100"
]
static let allowedSubnets = [
try! IPv4Address("10.0.0.0/8"),
try! IPv4Address("172.16.0.0/12"),
try! IPv4Address("192.168.0.0/16")
]
}
func validateClient(req: Request) throws -> Bool {
guard let remoteIP = req.remoteAddress?.ipAddress else {
throw Abort(.badRequest, reason: "No remote address")
}
// FIXED: Validate against IP whitelist
if SecurityConfig.allowedIPs.contains(remoteIP) {
return true
}
// FIXED: Check subnet ranges
guard let ipAddr = try? IPv4Address(remoteIP) else {
return false
}
for subnet in SecurityConfig.allowedSubnets {
if subnet.contains(ipAddr) {
return true
}
}
return false
}
-
Option 2: Mutual TLS (mTLS) Authentication, instead of weaker hostname/IP checks
import Vapor
import NIOSSL
// FIXED: Use client certificates instead of IP/hostname checks
func configureTLS(app: Application) throws {
let tlsConfig = TLSConfiguration.makeServerConfiguration(
certificateChain: loadServerCerts(),
privateKey: loadPrivateKey()
)
// FIXED: Require client certificates
tlsConfig.certificateVerification = .fullVerification
tlsConfig.trustRoots = .certificates(loadTrustedCACerts())
app.http.server.configuration.tlsConfiguration = tlsConfig
}
func authenticateClient(req: Request) -> Bool {
// FIXED: Verify client certificate
guard let clientCert = req.peerCertificate else {
return false
}
return verifyClientCertificate(clientCert)
}