tornado icon indicating copy to clipboard operation
tornado copied to clipboard

[Feature request] Options for block local request to prevent SSRF attack.

Open HuJK opened this issue 5 years ago • 1 comments

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)

HuJK avatar Sep 15 '20 09:09 HuJK

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.

bdarnell avatar Sep 19 '20 18:09 bdarnell