tornadoweb / tornadoweb/tornado

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

Open
#2,915 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

netutil
Dominant language
Python
Stars
22.2k
Forks
5.6k
Avg merge
3h 42m
Merged PRs (30d)
16

Description

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)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading Tornado's HTTP client connection path, including the tcp_client.connect flow shown in the issue; no source file or test is named. Trace redirects, hostname resolution, and connection establishment to determine where an IP-check hook and SSRF option would fit. Done means the requested protection handles redirects and DNS rebinding without requiring callers to reimplement redirects.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, networking, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.