def is_safe_hostname(hostname): """ Tests a hostname to ensure it doesn't appear to be a blacklisted IP range. """ # If we have no disallowed ips, we can skip any further validation # and there's no point in doing a DNS lookup to validate against # an empty list. if not DISALLOWED_IPS: return True if not hostname: return False hostname = ensure_fqdn(hostname) # Using the value from allowed_gai_family() in the context of getaddrinfo lets # us select whether to work with IPv4 DNS records, IPv6 records, or both. # The original create_connection function always returns all records. family = allowed_gai_family() try: for _, _, _, _, address in socket.getaddrinfo(hostname, 0, family, socket.SOCK_STREAM): # Only one bad apple will spoil the entire lookup, so be nice. if not is_ipaddress_allowed(address[0]): return False except (socket.gaierror, UnicodeError): # If we fail to resolve, automatically bad return False return True
def safe_create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None, socket_options=None): host, port = address if host.startswith("["): host = host.strip("[]") err = None host = ensure_fqdn(host) # Using the value from allowed_gai_family() in the context of getaddrinfo lets # us select whether to work with IPv4 DNS records, IPv6 records, or both. # The original create_connection function always returns all records. family = allowed_gai_family() for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res # HACK(mattrobenolt): This is the only code that diverges ip = sa[0] if not is_ipaddress_allowed(ip): # I am explicitly choosing to be overly aggressive here. This means # the first IP that matches that hits our restricted set of IP networks, # we reject all records. In theory, there might be IP addresses that # are safe, but if one record is straddling safe and unsafe IPs, it's # suspicious. if host == ip: raise RestrictedIPAddress("(%s) matches the URL blacklist" % ip) raise RestrictedIPAddress("(%s/%s) matches the URL blacklist" % (host, ip)) sock = None try: sock = socket.socket(af, socktype, proto) # If provided, set socket level options before connecting. _set_socket_options(sock, socket_options) if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: sock.settimeout(timeout) if source_address: sock.bind(source_address) sock.connect(sa) return sock except socket.error as e: err = e if sock is not None: sock.close() sock = None if err is not None: raise err raise socket.error("getaddrinfo returns an empty list")
def test_ip_family_ipv6_disabled(self): with patch("urllib3.util.connection.HAS_IPV6", False): assert allowed_gai_family() == socket.AF_INET
def test_ip_family_ipv6_enabled(self): with patch("urllib3.util.connection.HAS_IPV6", True): assert allowed_gai_family() == socket.AF_UNSPEC
def test_ip_family_ipv6_disabled(self): with patch('urllib3.util.connection.HAS_IPV6', False): assert allowed_gai_family() == socket.AF_INET
def test_ip_family_ipv6_enabled(self): with patch('urllib3.util.connection.HAS_IPV6', True): assert allowed_gai_family() == socket.AF_UNSPEC
def test_ip_family_ipv6_disabled(self): with patch('urllib3.util.connection.HAS_IPV6', False): self.assertEqual(allowed_gai_family(), socket.AF_INET)
def test_ip_family_ipv6_enabled(self): with patch('urllib3.util.connection.HAS_IPV6', True): self.assertEqual(allowed_gai_family(), socket.AF_UNSPEC)