[Feature request] Options for block local request to prevent SSRF attack.
I want to request a resource that provide by user. But I don't want let users access my local network resource to prevent SSRF attack.
Here is my workaround:
@functools.lru_cache(maxsize=100)
def check_sock(x):
s = socket.socket(x[0],x[1])
s.settimeout(1)
ret = s.connect_ex(x[-1])==0
s.close()
return ret
client = tornado.httpclient.AsyncHTTPClient()
if self.block_SSRF == True:
parsed = urllib.parse.urlsplit(_unicode(url))
if parsed.scheme not in ("http", "https"):
raise ValueError("Unsupported url scheme: %s" % url)
netloc = parsed.netloc
if "@" in netloc:
userpass, _, netloc = netloc.rpartition("@")
host, port = httputil.split_host_and_port(netloc)
if port is None:
port = 443 if parsed.scheme == "https" else 80
if re.match(r"^\[.*\]$", host):
host = host[1:-1]
host_ips = list(filter(lambda x:x[1]==socket.SOCK_STREAM,socket.getaddrinfo(host,port)))
host_ips_global = list(filter(lambda x:ipaddress.ip_address(x[-1][0]).is_global,host_ips))
host_ips_connectable = [x for x in host_ips_global if check_sock(x)]
if len(host_ips_global) == 0:
raise HTTPClientError(code=400,msg="SSRF blocked. Request to local network are blocked due to SSRF protection enabled")
if len(host_ips_connectable) == 0:
raise HTTPClientError(code=500,msg="Not connectable")
if "follow_redirects" in request and request["follow_redirects"] == True:
raise HTTPClientError(code=400,msg="SSRF blocked, follow_redirects are not available if SSRF protection enabled")
request["follow_redirects"] = False
host_ip = host_ips_connectable[0]
client = tornado.httpclient.AsyncHTTPClient(hostname_mapping={host:host_ip})
response = await client.fetch(url,**request)
But there still has a lot of problem. For example, if a external server returned me 301 Redirect 127.0.0.1:3679. My server will still access it and cause SSRF exploit.
Or I have to disable follow_redirects and reimplement follow_redirects by my self...
Even if I reimplement follow_redirects, check host ip on every request , Attackers can still use DNS Rebinding to attack my server......
Feautre request
Can I request a feature that there is a hook can let us check request IP before the request establish, and a flag for SSRF protection, to prevent requests access local resources?
In the source code of tornado, I want if the SSRF protection enabled, it will resolve host IP and check it before connection establish.
And connect to resolved IP directly instead resolve it again to prevent DNS Rebinding
if SSRF_protection == True:
host_ip_list = list(set([v[-1][0] for v in socket.getaddrinfo(host,0)])) # one host may resolved to multiple IP addresses.
check_host_ip(host_ip_list) #User can define this function
for index,host_ip in enumerate(host_ip_list):
try:
self.tcp_client.connect(host_ip , port, af=af,
ssl_options=ssl_options,
max_buffer_size=self.max_buffer_size,
callback=self._on_connect)
except Exception as e:
if index == len(host_ip_list) -1:
raise e
# fallback to next ip
else:
self.tcp_client.connect(host , port, af=af,
ssl_options=ssl_options,
max_buffer_size=self.max_buffer_size,
callback=self._on_connect)
Yes, this would be a good feature. I think you may be able to do something like this today with the Resolver interface: Make a wrapper resolver (similar to OverrideResolver) that checks the results of the real resolver and raises an error if it comes back with a private IP. I think the resolver gets called even if the URL contains a raw IP address so this should cover everything, including redirects.