Lucene search

K
hackeroneSnsnH1:887462
HistoryMay 30, 2020 - 1:08 p.m.

curl: curl overwrite local file with -J

2020-05-3013:08:39
snsn
hackerone.com
$700
54

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

4.6 Medium

CVSS2

Access Vector

LOCAL

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:L/AC:L/Au:N/C:P/I:P/A:P

0.001 Low

EPSS

Percentile

39.8%

Summary:

curl supports the Content-disposition header, including the filename= option. By design, curl does not allow server-provided local file override by verifying that the filename= argument does not exist before opening it.
However, the implementation contains 2 minor logical bugs that allow a server to override an arbitrary local file (without path traversal) when running curl with specific command line args (-OJi)
This bug can trigger a logical RCE when curl is used from the user’s home dir (or other specific directories), by overriding specific files (e.g. “.bashrc”), while keeping the user completely uninformed of the side effects.

The 2 bugs are:

  1. curl -iJ is not supported however curl -Ji is available -
  2. The standard Content-disposition handling flow does not allow opening existing files: https://github.com/curl/curl/blob/master/src/tool_cb_wrt.c#L54, however by using -OJi it is possible to reach a flow that overrides a local file with the response headers, without verification: https://github.com/curl/curl/blob/master/src/tool_cb_hdr.c#L196

Steps To Reproduce:

  1. Return the following http response form a server :
HTTP/1.1 200 OK
<PAYLOAD>
Content-disposition: attachment; filename=".bashrc"

Where <PAYLOAD> is the bash payload, e.g. echo pwn

  1. Run curl -OJi from the user’s home dir

Note that curl falsely claims that .bashrc was refused to be overwritten.

Supporting Material/References:

First bug:

    case 'i':
      config->show_headers = toggle; /* show the headers as well in the
                                        general output stream */
      break;
...
    case 'J': /* --remote-header-name */
      if(config->show_headers) {
        warnf(global,
              "--include and --remote-header-name cannot be combined.\n");
        return PARAM_BAD_USE;
      }
      config->content_disposition = toggle;
      break;

Second bug:

      if(filename) {
        if(outs->stream) {
          int rc;
          /* already opened and possibly written to */
          if(outs->fopened)
            fclose(outs->stream);
          outs->stream = NULL;

          /* rename the initial file name to the new file name */
          rc = rename(outs->filename, filename);
          if(rc != 0) {
            warnf(per->config->global, "Failed to rename %s -> %s: %s\n",
                  outs->filename, filename, strerror(errno));
          }
          if(outs->alloc_filename)
            Curl_safefree(outs->filename);
          if(rc != 0) {
            free(filename);
            return failure;
          }
        }

Impact

Local file override without path traversal, possibly leading to an RCE or loss of data.

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

4.6 Medium

CVSS2

Access Vector

LOCAL

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:L/AC:L/Au:N/C:P/I:P/A:P

0.001 Low

EPSS

Percentile

39.8%