3

I want to post a file to a server with a relative path supplied to the file's filename within the Content-Disposition header (using PHP 7.0 on Ubuntu with curl 7.47):

curl server/index.php -F "file=@somefile.txt;filename=a/b/c.txt"

Applying the --trace-ascii /dev/stdout option shows:

0000: POST /index.php HTTP/1.1
0031: Host: server
004a: User-Agent: curl/7.47.0
0063: Accept: */*
0070: Content-Length: 111511
0088: Expect: 100-continue
009e: Content-Type: multipart/form-data; boundary=--------------------
00de: ----e656f77ee2b4759a
00f4: 
...
0000: --------------------------e656f77ee2b4759a
002c: Content-Disposition: form-data; name="file"; filename="a/b/c.txt
006c: "
006f: Content-Type: application/octet-stream
0097: 
...

Now, my simple test script <?php print_r($_FILES["file"]); ?> outputs:

Array
(
    [name] => c.txt
    [type] => application/octet-stream
    [tmp_name] => /tmp/phpNaikad
    [error] => 0
    [size] => 111310
)

However, I expected [name] => a/b/c.txt. Where is the flaw in my logic?

According to https://stackoverflow.com/a/3393822/1647737 the filename can contain relative path.

The PHP manual also implies this and suggests sanitizing with basename().

le_m
  • 19,302
  • 9
  • 64
  • 74
  • I think you can't get original path of uploaded file due security reason. Nor php, nor javascript can't retrieve this info. – Wizard Aug 29 '17 at 01:18
  • @Wizard You are right for uploads from within a browser; apart from old IE versions no browser sends the full local path. But here the truncation must happen on the server side (as you can see from the trace with the full path included). – le_m Aug 29 '17 at 01:24
  • 1
    yep, I see that.. but I suspect php-side cut this off due the same reason. You can [read the code of php-interpreter](https://github.com/php/php-src/blob/c8aa6f3a9a3d2c114d0c5e0c9fdd0a465dbb54a5/main/rfc1867.c#L1151) – Wizard Aug 29 '17 at 01:34
  • @Wizard Thanks for the reference, will read it. If PHP is indeed the culprit, then this behavior might have changed over time as the manual and other sources still recommend the basename() sanitization (?) EDIT: Just read the source - can't believe the argument is old IE and the comment mentions removing this behavior in the future... – le_m Aug 29 '17 at 01:37
  • @Wizard Would you like to post your comment as an answer so I can accept it? If not, I'll write an answer later with reference to your comment. Thanks for the pointer to that exact location in the PHP interpreter source! – le_m Aug 29 '17 at 01:45

1 Answers1

1

As we can see from the php-interpreter sources, _basename() filter invoked for security reason and/or to fix some cons particular browsers.

File: php-src/main/rfc1867.c

Lines ~1151 and below:

        /* The \ check should technically be needed for win32 systems only where
         * it is a valid path separator. However, IE in all it's wisdom always sends
         * the full path of the file on the user's filesystem, which means that unless
         * the user does basename() they get a bogus file name. Until IE's user base drops
         * to nill or problem is fixed this code must remain enabled for all systems. */
        s = _basename(internal_encoding, filename);
        if (!s) {
            s = filename;
        }
Community
  • 1
  • 1
Wizard
  • 862
  • 6
  • 9