HexBrain

curl · Report #4124849

CVE-2026-111951: curl -OJ truncate without filename

Validstatus
lowseverity
4upvotes
26 Oct 2026disclosed

How it was found

Summary:

With -O -J, when a response carries a Content-Disposition header without a usable filename= (for example inline) before the one that names an existing file, curl truncates that existing file to 0 bytes and then exits 23 saying it refused to overwrite it. This breaks the promise in docs/cmdline-opts/remote-header-name.md: "If the server specifies a filename and a file with that name already exists in the destination directory, it is not overwritten and an error occurs".

The cause is src/tool_cb_hdr.c lines 405 to 408 in curl-8_23_0: when the header has no filename=, the code falls through to tool_create_output_file(), which opens the URL-derived name with fopen(fname, "wb") (src/tool_cb_wrt.c line 87) because is_cd_filename is still FALSE. The following filename="victim" header is then refused by set_filename() since the stream is already open, but the file is already empty. The same happens with -L when a 302 carries Content-Disposition: inline before Location:, with filename*= first, and over HTTPS; with -i the file gets the response headers written into it.

AI assistance was used for testing and code review; the writing and findings are mine.

Affected version

curl 8.23.0 (also 8.22.0 and master 70f3f855c6), macOS, OpenSSL 3.6.5 build. Only the curl tool.

Steps To Reproduce:

  1. Run any HTTP server on 127.0.0.1:32700 that answers:
HTTP/1.1 200 OK
Content-Disposition: inline
Content-Disposition: attachment; filename="victim"
Content-Length: 8

PAYLOAD
  1. Run:
printf 'ORIGINAL-CONTENT\n' > victim
curl -sS -OJ http://127.0.0.1:32700/victim; echo "exit=$?"; wc -c victim
  1. Result:
curl: (23) client returned ERROR on write of 52 bytes
exit=23
0 victim

Control: with only Content-Disposition: attachment; filename="victim" curl exits 23 and victim stays 17 bytes.

Proposed fix:

--- a/src/tool_cb_hdr.c
+++ b/src/tool_cb_hdr.c
@@ -405,8 +405,12 @@ static size_t content_disposition(const char *str, const char *end,
       }
       break;
     }
-    if(!outs->stream && !tool_create_output_file(outs, per->config))
-      return CURL_WRITEFUNC_ERROR;
+    if(!outs->stream) {
+      if(hdrcbdata->config->show_headers)
+        return buffer_header(hdrcbdata, str, cb);
+      return cb;
+    }
     if(tool_write_headers(&per->hdrcbdata, outs->stream))
       return CURL_WRITEFUNC_ERROR;

buffer_header() needs a forward declaration above content_disposition(). With this patch every case above leaves the file intact, and a Content-Disposition with no filename at all still falls back to the URL name.

Impact

Summary:

A user running curl -OJ https://host/report.pdf (or with -L) in a directory that already holds report.pdf gets that file truncated to 0 bytes by a malicious or compromised server, while curl reports that it refused to overwrite it.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original