DEV Community

kozhevniko
kozhevniko

Posted on

Tornado 6.5.9: A Symlink, a Response Ceiling, and a Query String

Tornado 6.5.9: A Symlink, a Response Ceiling, and a Query String

Tornado is a web framework and an asynchronous HTTP client in one package, and three advisories published on 30 September 2026 cover all three roles. Two of them are in framework code that many applications inherit without choosing it, and one is in the client used to fetch content from elsewhere. A fourth advisory in the same window covers GitPython.

A path check that resolved the wrong string

The most directly exploitable is CVE-2026-8528 in tornado.web.StaticFileHandler. The handler's get_absolute_path uses os.path.abspath() on the requested path, and validate_absolute_path uses the same function on the root before performing a string prefix check.
os.path.abspath() normalises . and .. segments but does not resolve symbolic links. A path such as /var/www/static/link therefore passes a startswith("/var/www/static/") test regardless of where link points. Immediately afterwards, os.path.exists() and os.path.isfile() do follow symlinks, so the file actually opened is the link target.
The reproduction in the advisory is three commands:

mkdir -p /tmp/static
echo "DB_PASSWORD=s3cr3t" > /tmp/secret.conf
ln -s /tmp/secret.conf /tmp/static/config.conf
Enter fullscreen mode Exit fullscreen mode

A minimal application that serves /tmp/static then returns the secret at /static/config.conf. The advisory names /etc/passwd, private keys, configuration files and application secrets as reachable, bounded only by the filesystem permissions of the process user.
Two exposure conditions matter. The static directory contains symlinks pointing outside it, which the advisory notes is common with build tooling such as npm link, webpack, Docker volume mounts and CDN sync tools. Or the application allows file uploads into the static directory without stripping symlinks. The proposed fix is to replace os.path.abspath() with os.path.realpath() in both methods, so the resolved target is validated against the root. Fixed in 6.5.9.

A client with no ceiling on the response

CVE-2026-8529 concerns CurlAsyncHTTPClient in the same package. The advisory states it enforces no response-size limit, so a compressed response expands without bound in memory. This is a decompression bomb against a client rather than a server, which changes who is exposed: any service that fetches a URL it does not fully control, including webhooks, link previews and feed readers, becomes a target.

A parser that scales with the number of arguments

CVE-2026-8530 covers the query string. Tornado parses query-string arguments, and with no bound on argument count, a request carrying a very large number of arguments stalls the event loop. The impact is availability, and it is notable that this is a loop-level stall rather than a per-request cost, so a single request can affect other connections served by the same process.

The same shape outside a web framework

GHSA-59cr-6r3x-644w describes a path traversal in GitPython's submodule update that writes outside the repository. An attacker-influenced submodule path escapes the working tree during an update, so the exposure depends on where source is fetched from rather than on any network-facing service.

What to check

Upgrade Tornado to 6.5.9, which carries the static file fix, and check whether the application's static root is populated by tooling that creates symlinks. That second check is the one most likely to change an assessment: a deployment that never places a link inside the static directory is not exposed to CVE-2026-8528 regardless of the version in use.
For the client and query-string issues, the useful reduction is at the edge. A response-size cap on outbound fetches, and a limit on query-string length at the reverse proxy, both remove the reachable path without waiting for a dependency bump. GitPython users should treat submodule updates across a trust boundary as a review point.

References

Top comments (0)