I am using a flash player to play some mp3 files. At firefox it loads them normally but at IE it doesn't. When i go to the url of the .mp3 file it shows the source code of the mp3 (instead of offering eg to download). So i used a small script to fix it:
$url = $_GET['url'];
header('Content-type: application/force-download');
header('Content-Transfer-Encoding: Binary');
header("Content-disposition: attachment; filename=demo.mp3");
readfile($url);
I would like to ask you if the above is safe. Moreover, does the server losses bandwidth by this way? And finally, does it influence the server's resources?
Thanks.
No, that's not safe. If you had your database password in database.php and I entered database.php as $_GET['url'], your script would send me that PHP file with your password in it.
Yes, this would use up bandwidth and some server resources.
It's not safe, and it shouldn't be necessary for you to do this way.
In addition to the security implications #ceejayoz outlines, if the allow_url_fopen PHP setting is enabled, it is also possible to insert any URL into $url. That way, your server could be easily misused to stream large amounts of data from other servers, with all kinds of implications.
This method of serving files should be used only when really necessary. It consumes more resources (because an expensive PHP process has to be started) than requesting a static resource through the web server.
It should not be necessary in your case anyway. It sounds like your web server is not serving the correct content-type header along with your MP3 files. That is what you should fix.
Maybe, if you're on Apache, adding a .htaccess file to the directory the MP3s are in with the following content:
AddType audio/mpeg .mp3
already fixes the problem. If it doesn't, but the force-download thing works, then try
AddType application/force-download .mp3
Your actual problem is that you are not sending the content-type header to the client when you serve the mp3 file. Ensure that you are setting the content-type header prior to sending the contents of the mp3 file.
If you're serving them directly from your web server, without a script, you simply need to configure the content-type in your web server's configuration.
For Apache, you can configure this in an .htaccess file:
AddType audio/mpeg .mp3
Yeah there is definitely a security risk here since you aren't validating/sanitizing the requested file path. So make sure you check that before sending files down to the user!
Although this will use bandwidth and server resources, it would be minimally more than downloading files regularly. The only extra overhead is processing/running the PHP. You probably won't notice a difference.
Related
I recently came across How to force file download with PHP which describes setting Content-Transfer-Encoding. Is this header applicable when used with this application when used to download a file via HTTP (it appears to be email related)? If so, what should it be set as for MS Word and Excel files, ZIP files, PDFs, and Text?
header("Content-Transfer-Encoding: Binary");
It has been religiously included by carg-cult copy-paste programmers since it was mentioned in a comment to the readfile() manpage on PHP.net.
It indeed has little to do with HTTP and you can safely remove it.
On my server I have a directory with music files, generally in .mp3 format. I'm writing a web application to search for and play these tracks.
All the files are stored, with their absolute server path, artist, album and title info in a MySQL database.
What I want to do is have a PHP file that "outputs" an mp3 file on the server that would normally be inaccessible from the web. So, this is what I want to achieve:
client requests play.php?id=10
PHP gets absolute server path from MySQL database where ID = 10
PHP outputs the file (which would really be at e.g. '/home/user/files/no_web/mp3/Thing.mp3')
To the client, it looks like there is an mp3 file called http://myserver.com/play.php?id=10 and it starts to play.
I'm sure this sort of thing is possible, just not sure how. Thanks in advance :)
You need to send correct content-type header and then just output the file:
header('Content-type: audio/mpeg3');
readfile('filename.mp3');
For reading the file and sending it, you can use the readfile function.
For setting the mime-type, so the browser actually knows what type of file is sent by the webserver, use the header function like:
header('Content-Type: audio/mpeg');
Additionally, you may also want to set the Content-Length HTTP header.
header('Content-Length: ' . filesize($filepath) );
If all you're trying to do is let the user download the mp3, just use the readfile command which will read the mp3 file and pass it along to the client. However you need to make sure to set the mime-type correctly.
I have .mp3 files in my website and I want to set my site so that after my users have logged in they can download files. If users are not logged in they won't be able to download files. I do not want anyone to be able to find the path of the files.
I'd make the file impossible to access via an HTTP request alone, and with PHP, just print it out:
<?php
session_start();
if (isset($_SESSION['logged_in'])) {
$file = '/this/is/the/path/file.mp3';
header('Content-type: audio/mpeg');
header('Content-length: ' . filesize($file));
readfile($file);
}
?>
You can create a token based on something like the user session id and some random value. Then, the logged in user urls would be like :
/download.php?token=4782ab847313bcd
Place the MP3 files above your docroot, or if that is impossible, deny access to them with .htaccess (if using Apache).
Verify a user is logged in.
Send the appropriate headers and readfile() on the MP3 when the user requests the file.
From wordpress.stackexchange.com/a/285018
Caution: Be wary of using this PHP-driven file download technique on larger files (e.g., over 20MB in size). Why? Two reasons:
PHP has an internal memory limit. If readfile() exceeds that limit when reading the file into memory and serving it out to a visitor, your script will fail.
In addition, PHP scripts also have a time limit. If a visitor on a very slow connection takes a long time to download a larger file, the script will timeout and the user will experience a failed download attempt, or receive a partial/corrupted file.
Caution: Also be aware that PHP-driven file downloads using the readfile() technique do not support resumable byte ranges. So pausing the download, or the download being interrupted in some way, doesn't leave the user with an option to resume. They will need to start the download all over again. It is possible to support Range requests (resume) in PHP, but that is tedious.
In the long-term, my suggestion is that you start looking at a much more effective way of serving protected files, referred to as X-Sendfile in Apache, and X-Accel-Redirect in Nginx.
X-Sendfile and X-Accel-Redirect both work on the same underlying concept. Instead of asking a scripting language like PHP to pull a file into memory, simply tell the web server to do an internal redirect and serve the contents of an otherwise protected file. In short, you can do away with much of the above, and reduce the solution down to just header('X-Accel-Redirect: ...').
If I put this tag in my HTML:
<img src="http://server/imageHandler.php?image=someImage.jpg" />
instead of
<img src="http://server/images/someImage.jpg" />
and use the imageHandler.php script to read the image file, output expire headers and print the image data, i expect it to be slower. Is this a viable way to manipulate image headers or the performance penalty is too much since images are all around.
Are there other ways to set expire headers on images through my php code?
Yes, running any PHP script is slower than serving a static file.
Instead, you can set expire headers using Apache configuration directives. See the documentation for mod_expires. For example, you can put the following directives in an Apache httpd.conf or .htaccess file:
# enable expirations
ExpiresActive On
# expire JPG images after a month in the client's cache
ExpiresByType image/jpg A2592000
Try something like
header("Expires: <the date>");
http_send_file("someImage.jpg");
from http://us3.php.net/manual/en/function.http-send-file.php The http_send_file function (if it works the way I think it does) copies the file directly from disk to the network interface, so it sends the image itself about as fast as the server would natively. (Of course, the time taken to run the PHP interpreter will still make the overall request a little slower than serving a static file.)
As at least one other answer has mentioned, probably the best way to set an Expires header (or any other header), if you're running Apache, is to use an .htaccess file, or better yet if you have access to the main configuration files of the server, put the Expires configuration there. Have a look at mod_expires.
If the imagehandler is using GD or Imagick it will be considerably slower than a regular img tag.
I would suggest caching the image output to a temporary file, if the file exists, the imagehandler just outputs that raw data but if a new file should be created, then it is generated once and then used again for the cache.
You can use htaccess to set expiry.
.htaccess Caching
That would depend on what task imageHandler.php performs. YOu can measure it by printing out the time in milliseconds in your code.
The company I work for has recently been hit with many header injection and file upload exploits on the sites we host and while we have fixed the problem with respect to header injection attacks, we have yet to get the upload exploits under control.
I'm trying to set up a plug-and-play-type series of upload scripts to use in-house that a designer can copy into their site's structure, modify a few variables, and have a ready-to-go upload form on their site. We're looking to limit our exposure as much as possible (we've already shut down fopen and shell commands).
I've searched the site for the last hour and found many different answers dealing with specific methods that rely on outside sources. What do you all think is the best script-only solution that is specific enough to use as a reliable method of protection? Also, I'd like to keep the language limited to PHP or pseudo-code if possible.
Edit: I've found my answer (posted below) and, while it does make use of the shell command exec(), if you block script files from being uploaded (which this solution does very well), you won't run into any problems.
Allow only authorized users to upload a file. You can add a captcha as well to hinder primitive bots.
First of all, set the MAX_FILE_SIZE in your upload form, and set the maximum file size and count on the server as well.
ini_set('post_max_size', '40M'); //or bigger by multiple files
ini_set('upload_max_filesize', '40M');
ini_set('max_file_uploads', 10);
Do size check by the uploaded files:
if ($fileInput['size'] > $sizeLimit)
; //handle size error here
You should use $_FILES and move_uploaded_file() to put your uploaded files into the right directory, or if you want to process it, then check with is_uploaded_file(). (These functions exist to prevent file name injections caused by register_globals.)
$uploadStoragePath = '/file_storage';
$fileInput = $_FILES['image'];
if ($fileInput['error'] != UPLOAD_ERR_OK)
; //handle upload error here, see http://php.net/manual/en/features.file-upload.errors.php
//size check here
$temporaryName = $fileInput['tmp_name'];
$extension = pathinfo($fileInput['name'], PATHINFO_EXTENSION);
//mime check, chmod, etc. here
$name = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); //true random id
move_uploaded_file($temporaryName, $uploadStoragePath.'/'.$name.'.'.$extension);
Always generate a random id instead of using the original file name.
Create a new subdomain for example http://static.example.com or at least a new directory outside of the public_html, for the uploaded files. This subdomain or directory should not execute any file. Set it in the server config, or set in a .htaccess file by the directory.
SetHandler none
SetHandler default-handler
Options -ExecCGI
php_flag engine off
Set it with chmod() as well.
$noExecMode = 0644;
chmod($uploadedFile, $noExecMode);
Use chmod() on the newly uploaded files too and set it on the directory.
You should check the mime type sent by the hacker. You should create a whitelist of allowed mime types. Allow images only if any other format is not necessary. Any other format is a security threat. Images too, but at least we have tools to handle them...
The corrupted content for example: HTML in an image file can cause XSS by browsers with content sniffing vulnerability. When the corrupted content is a PHP code, then it can be combined with an eval injection vulnerability.
$userContent = '../uploads/malicious.jpg';
include('includes/'.$userContent);
Try to avoid this, for example use a class autoloader instead of including php files manually...
By handling the javascript injection at first you have to turn off xss and content sniffing in the browsers. Content sniffing problems are typical by older msie, I think the other browsers filter them pretty well. Anyways you can prevent these problems with a bunch of headers. (Not fully supported by every browser, but that's the best you can do on client side.)
Strict-Transport-Security: max-age={your-max-age}
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-XSS-Protection: 1; mode=block
Content-Security-Policy: {your-security-policy}
You can check if a file is corrupted with Imagick identify, but that does not mean a complete protection.
try {
$uploadedImage = new Imagick($uploadedFile);
$attributes = $uploadedImage->identifyImage();
$format = $image->getImageFormat();
var_dump($attributes, $format);
} catch (ImagickException $exception) {
//handle damaged or corrupted images
}
If you want to serve other mime types, you should always force download by them, never include them into webpages, unless you really know what you are doing...
X-Download-Options: noopen
Content-Disposition: attachment; filename=untrustedfile.html
It is possible to have valid image files with code inside them, for example in exif data. So you have to purge exif from images, if its content is not important to you. You can do that with Imagick or GD, but both of them requires repacking of the file. You can find an exiftool as an alternative.
I think the simplest way to clear exif, is loading images with GD, and save them as PNG with highest quality. So the images won't lose quality, and the exif tag will be purged, because GD cannot handle it. Make this with images uploaded as PNG too...
If you want to extract the exif data, never use preg_replace() if the pattern or replacement is from the user, because that will lead to an eval injection... Use preg_replace_callback() instead of the eval regex flag, if necessary. (Common mistake in copy paste codes.)
Exif data can be a problem if your site has an eval injection vulnerability, for example if you use include($userInput) somewhere.
Never ever use include(), require() by uploaded files, serve them as static or use file_get_contents() or readfile(), or any other file reading function, if you want to control access.
It is rarely available, but I think the best approach to use the X-Sendfile: {filename} headers with the sendfile apache module. By the headers, never use user input without validation or sanitization, because that will lead to HTTP header injection.
If you don't need access control (means: only authorized users can see the uploaded files), then serve the files with your webserver. It is much faster...
Use an antivir to check the uploaded files, if you have one.
Always use a combined protection, not just a single approach. It will be harder to breach your defenses...
The best solution, IMHO, is to put the directory containing the uploaded files outside of the "web" environment and use a script to make them downloadable. In this way, even if somebody uploads a script it can not be executed by calling it from the browser and you don't have to check the type of the uploaded file.
Use and configure Hardened-PHP create a plain script using move_uploaded_file and the $_FILES superglobal. The simplest the script, the safest it will be (at least, as safe as the running PHP version itself)