Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.4] Prevent PHP file extensions uploads by default unless explicitly allowed #20392

Merged
merged 5 commits into from
Aug 2, 2017
Merged

Conversation

laurencei
Copy link
Contributor

@laurencei laurencei commented Aug 2, 2017

This is an attempt to fix #20390, #18120 and #18299

Basically during MIME validations we now block any file that ends in .php unless you have explicitly allowed a php MIME. This solves the image spoofing issues, where you can make a php file pass the image validation MIME.

Seems like a sensible default, and if someone is brave enough to want to allow a .php file they still can by explicitly setting it. It doesnt solve general malicious uploads (like a bad pdf file or something) - but I think that .php extensions are a special case, since it can result in remote execution on the server.

Should be backwards compatible - on the basis it is unlikely anyone wanted to allow a .php file in the first place?

Before:
'picture' => 'required|image

  • #c5f015 fake.php could pass in some situations

'picture' => 'required|mimes:gif

  • #c5f015 fake.php could pass in some situations

'picture' => 'required|mimes:gif,php

  • #c5f015 fake.php will pass

After:
'picture' => 'required|image

  • #f03c15 fake.php can no longer pass at all

'picture' => 'required|mimes:gif

  • #f03c15 fake.php can no longer pass at all

'picture' => 'required|mimes:gif,php

  • #c5f015 fake.php will pass

Note:

'picture' => 'required|file

  • #c5f015 fake.php will still pass before/after.

return $value->getPath() != '' &&
(in_array($value->getMimeType(), $parameters) ||
in_array(explode('/', $value->getMimeType())[0].'/*', $parameters));
}

/**
* Check if we have explicity allowed a PHP upload, and check accordingly.
*
* @param string $filename
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

phpdoc is not correct

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks - fixed now.

@Numline1
Copy link

Numline1 commented Aug 2, 2017

@laurencei this seems great, however, what about edge cases where other than .php files are processed as PHP? Legacy systems with .php5 extensions come to mind.

@laurencei laurencei changed the title [5.4] Prevent Php file extensions by default unless explicitly alowed [5.4] Prevent PHP file extensions uploads by default unless explicitly allowed Aug 2, 2017
@laurencei
Copy link
Contributor Author

laurencei commented Aug 2, 2017

If they are that legacy then they can probably no longer run Laravel? 😄

I could add it - but I'm not sure. I'll see if Taylor likes this concept at all, and he can decide.

@taylorotwell
Copy link
Member

I'm not sure I really see this as a framework issue if someone is calling system($file)?

@laurencei
Copy link
Contributor Author

laurencei commented Aug 2, 2017

@taylorotwell - I'll send you an email with how the exploit could work.

edit: email sent to [email protected]

@taylorotwell taylorotwell merged commit bbf5b67 into laravel:5.4 Aug 2, 2017
@barryvdh
Copy link
Contributor

barryvdh commented Aug 4, 2017

Would it be appropriate to include a default .htaccess file for the public storage folder, which defines a SetHandler to avoid PHP execution for all files in that folder (with Apache), like Drupal? https://www.drupal.org/node/652002

@barryvdh
Copy link
Contributor

barryvdh commented Aug 4, 2017

Are you going to backport this to 5.1/5.2/5.3?

@HSPDev
Copy link
Contributor

HSPDev commented Aug 4, 2017

This is the "security vulnerability" I reported on e-mail about 1,5 month ago Taylor, where we couldn't come up with a good way to fix it.
Happy to see @laurencei was able to find a nice clean fix. - Thanks. 👍

I'll probably still instruct my developers to always write another explicit in_array() check of the extension before manipulating files. We are working with some semi-big clients and like to be verbose rather than sorry, for the exact reason of this fix.

This still applies to lots of other formats and I've always found the inspection of byte patterns in the file uploaded problematic. I can understand trying to guess the real file type and not rely on extensions, as Linux and Mac etc. kan easily have files that the user uses normally without an extension if they messed up the naming. Now we are basically building a blacklist of file extensions, but regarding security we should really be whitelisting.

What if we made a list of "known" extensions of the different mimes, and threw and exception on getClientOriginalExtension() if it didn't match any of the ones the user tried to filter by? Possibly even make an getClientOriginalReallyUnsafeExtensionBeSureAboutWhatYouAreDoing() function for the original functionality. (Semi serious about the naming).

@laurencei
Copy link
Contributor Author

laurencei commented Aug 4, 2017

A general approach could be that the file extension needs to match the expected MIME. i.e. if you upload a file that has a MIME of pdf - then we only accept an extension of .pdf. If it has another other extension - it is rejected.

Same goes in reverse. If you upload a .pdf - then the MIME should match pdf - otherwise something is wrong. Therefore is someone uploads a file that has a MIME of xls but an extension of .exe - it would be automatically rejected. You can use the standard PHP MIME/Extension list to achieve this - would probably capture most situations?

So its a double level protection in addition to what exists now. We dont rely on the MIME or the extension - we rely on both, and force both to match. Could be called strictMimeValidation or something as a rule.

@laurencei
Copy link
Contributor Author

laurencei commented Aug 4, 2017

Are you going to backport this to 5.1/5.2/5.3?

@barryvdh - I had a quick look. The code would be the same, but it would need to be another PR, as the files are different (they were refactored for 5.4 I think).

I'm not able to do it right now as I'm about to go out for a while - so if someone else wants to do it? (And if Taylor agrees to the backport obviously).

@barryvdh
Copy link
Contributor

barryvdh commented Aug 4, 2017

I think you should probably always want that, unless you explicitly disable it. Never allowing php is a start, but perhaps there are other scripts that could be configured to execute. And not just images, but what if someone modifies a pdf to pass mime type?

@laurencei
Copy link
Contributor Author

laurencei commented Aug 4, 2017

Never allowing php is a start, but perhaps there are other scripts that could be configured to execute. And not just images, but what if someone modifies a pdf to pass mime type?

If the webserver is correctly setup - then only a .php extension is parsed for PHP. So even if I upload a php file with a .gif extension - it shouldnt be possible to run it as the web-user - because the file is never run through the PHP system.

That's why we explicitly block .php for now. It's realistically the only extension that can hurt most webservers.

@barryvdh
Copy link
Contributor

barryvdh commented Aug 4, 2017

If the webserver is correctly setup - then only a .php extension is parsed for PHP. So even if I upload a php file as a .gif - it shouldnt be possible to run it as the web-user.

Yeah I was thinking about python (.py) etc, but I guess you're right that it's mostly just PHP we need to worry about, with default configs.

Doesn't Symfony have the same problem with their Mime validation, or do they fix it differently? Can't we use that/

@laurencei
Copy link
Contributor Author

laurencei commented Aug 4, 2017

Doesn't Symfony have the same problem with their Mime validation, or do they fix it differently?

AFAIK - they suffer the same issue - as the problem is with MIME's themselves - not Symfony or PHP. But this is starting to get beyond my knowledge on the topic - so take that with a grain of salt... maybe someone else knows more they can join in...

@Numline1
Copy link

Numline1 commented Aug 4, 2017

@barryvdh most Nginx configurations only check the .php extension and then pass the script to an fpm instance. Apache probably checks mime and extension when using mod_php.

This makes me wonder, as I mentioned in the original thread: Wouldn't it be smarter to leave the image rule as it was and instead add better validation for mimes? I'm not sure if PHP's mime handling allows this, so maybe adding an "extension" validation rule would be a nice thing.

"image" rule could then validate mimes and extensions which would were whitelisted, instead of blacklisting .php.

@laurencei
Copy link
Contributor Author

Ok - backports done.

@vlakoff
Copy link
Contributor

vlakoff commented Aug 5, 2017

You should also block .PhP (case insensitive, for windows boxes), .php5, .php7, etc.

Ideally it could be /^php\d*$/i

@laurencei
Copy link
Contributor Author

@vlakoff - we already do a case insensitive check with strtolower()

@vlakoff
Copy link
Contributor

vlakoff commented Aug 5, 2017

Not in the in_array part. I haven't examined further if this is fine or not.

@laurencei
Copy link
Contributor Author

laurencei commented Aug 5, 2017

Yes - because the array part is our definition for a mime allowing php - not the users.

And a correctly configured server should only run .php as an extension. .php7 etc shouldnt be allowed to run anyway. I just checked - and that is how it is on nginx.

@Numline1
Copy link

Numline1 commented Aug 5, 2017

Yup. Some shared hostings actually run .php7 and .php5 (or other versions for whatever reason), but I think that's a borderline nonexistent minority.

I would however like to hear about what people thing about my last comment, considering whitelisting instead of blacklisting (blacklisting seems like a tempfix).

@taylorotwell if you'd be okay using mime+extension for validation, I'd be okay with creating the PR, the only issue that comes in mind is people uploading images with non-standard or empty extensions.

@laurencei
Copy link
Contributor Author

@Numline1 - I was thinking of maybe making mime "strict" in 5.5 - and have like softMime validation rule or something that isnt as stict as a fallback...

@Numline1
Copy link

Numline1 commented Aug 5, 2017

Not sure if I mentioned this before, but perhaps making an "extension" rule, which then could be used separately or aliased in "image" rule would be nice, although I'm not sure if combining rules is a good idea (how would "image" with another separate "extension" rule behave in form request if "extension" rule was already used in "image" in the framework code?).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants