Preventing users from abusing an image deletion script - php

Basically the following for the core part:
$file = basename($_GET['f']);
$directory = "/var/www/site/";
$file = $directory . $file;
$hash = $_GET['h'];
$md5check = md5($file);
$md5check = substr($md5check, 0, 5);
if ($md5check == $hash) {
if (file_exists($file)) {
unlink($file);
}
else {
die('error');
}
}
else {
header('Location: error');
exit;
}
I realise using the users input is asking for trouble, but how can I get the server to 'locate' the file to delete? Am I somehow able to escape injections?
The user would be loading http://site.com/?f=test.jpg&h=hashc
Also is there any other hash systems besides MD5 which is separate for each location of a file?
eg.
file1.rar downloaded at 12:00am = differenthash
file1.rar downloaded at 11:00pm = randomhash
file1.rar is the same file in both scenarios.
versus md5:
file1.rar downloaded at 12:00am = randomhash
file1.rar downloaded at 11:00pm = randomhash
file1.rar is the same file in both scenarios.

You're already using basename which should limit the attack vector greatly (as the user can't delete files from a different directory), however letting the user have access to delete files from /var/ is a very bad idea as the user would be able to pass any non-image file across too.
Can you not have some path relative to your web root rather than a very important system directory?
Extra security could include (note that this list is not at all exhaustive..):
User checking: Check that the web server user owns the file the user is requesting to delete.
Store uploaded files in the database and check that they exist and have been uploaded by our script before allowing deletion.
As above, move the files out of the system directory.
Use stronger hashing (ie. salts!).
Restrict this to a logged in user and log all actions, if somebody tries to delete a file it's logged and you know exactly who it was.

As #rudi_visser said, best way is white-listing, And store the uploaded files+data in the database.
And when the user tries to delete the file, make sure it's him, and make sure the file is uploaded (exists in the "uploaded" table not a part of your script/system).

Related

Changing file permissions in php

I'm a sysadmin for a small firm and I manage the server for them.
I've setup a portal for our customers to view their bills in pdf format, they are initially set with 0600 file permissions. For security reasons I cannot have all the pdf's 'visible' to everyone so I need a way to show them to the customer only when a pdf is clicked on the customers' account.
I have tried using the following, but it doesn't work and I'm getting a 'Forbidden' error...
chmod($filename, 0755);
echo "<td><iframe src='" . $filename . "' width=645 height=600 frameborder=0></iframe></td>";
chmod($filename, 0600);
The php script and the pdf files have the same owner set.
Any ideas what I'm doing wrong, I need to get this working?!
Many thanks! :)
This can not possibly work:
chmod($filename, 0755);
echo "<td><iframe src='" . $filename . "' width=645 height=600 frameborder=0></iframe></td>";
chmod($filename, 0600);
You're making the file readable only for the amount of time it takes PHP to echo that one line of HTML. I.e., by the time the user clicks the link, permissions have already been revoked again. On top of that, the file is world-readable for that period of time, so anybody on the Internet can see it.
To do this more securely, do not have the web server serve the files directly, as you will not be able to control who has access to them. Instead, put them outside the document root so that they can not be seen at all by the web server, and then proxy them through a PHP script (via readfile() or similar) that performs an ownership check.
In your script that generates the link:
echo 'PDF Download';
Where $fileId is some unique identifier for the file, but not the full file name.
Then, in download.php, something like this:
function getLoggedInUser() {
// return the logged-in user
}
function getFileForId($fileId) {
// get the full path to the file referenced by $fileId
}
function getOwnerOfFile($fileId) {
// get the user allowed to see the file referenced by $fileId
}
$fileId = $_GET['fileId'];
$file = getFileForId($fileId);
if (!file_exists($file)) {
header('HTTP/1.1 404 Not Found');
exit;
}
if (getLoggedInUser() !== getOwnerOfFile($fileId)) {
header('HTTP/1.1 403 Forbidden');
exit;
}
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="whatever.pdf"');
readfile($file);
[UPDATE]
and I have <a href="/viewbill.php?bid=<?php echo $invoice_number; ?>" title="View PDF Invoice"> where the $invoice_number is the name of the file.
That's fine, just make sure that viewbill.php performs a check to ensure that the logged-in user is the same as the user that the bill is for, otherwise any customer can view any other customer's bills simply by changing the invoice number in the URL.
When you say 'put them outside the document root' where do you mean exactly;
Let's say that your Apache document_root directive points to /var/htdocs/public/. In this case, everything in that directory and everything under it can be seen by Apache and potentially served directly to a client. E.g., if you have a PDF file in /var/htdocs/public/pdfs/12345.pdf then a user can simply request the URL /pdfs/12345.pdf in their browser, regardless of what PHP structures are in place. Often this is mitigated with the use of .htaccess files but this is not ideal. So, if you have files that you want to keep controlled, you should not put them anywhere under the document_root. For example, put them in /var/htdocs/docs/ instead. This way, Apache can not possibly see them, but you can still use readfile() to pull their contents.

PHP link/request to download file then delete it immediately

I face a case I never did, and I dont know how to properly do it.
I have a php script which generate files for clients. At the end of the script, I echo the path for them to download the file, simply.
How can I do to provide the file - or the path or any what - for downloading it, and be sure to delete the file once downloaded.
Widely, I'd like to make the file available for one/unique download only. How to ?
EDIT
I cannot use headers
There are a few components to getting this to work. Without knowing which framework you use, I'll use comments as placeholders.
There is no way to do it without using the header function, though.
Here is the source for a file that outlines the process:
<?php
$fileid = $_GET['fileid'];
$key = $_GET['key'];
// find the file in the database, and store it in $file
if ($keyMatches) {
// it is important for security to only use file paths from the database
$actualPath = $file->getPathOnDisk();
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($fileInfo, $actualPath);
$fp = fopen($actualPath, 'rb');
header("Content-Type: " . $mime);
header("Content-Length: " . filesize($actualPath));
fpassthru($fp);
}
else
{
http_response_code(403); // forbidden
}
You'll use this by linking to download.php?fileid=1234&key=foobar, and generating the URL at the same time you generate the key and store it in the database.
For security, you'll keep the files outside of the web root, meaning they cannot be accessed through the web server without going through a script.
fpassthru is reasonably fast, and will not likely have a performance impact.
You must do a download file gateway, like download.php?id=XXX
Where XXX is the unique ID of each file you will store in DB. And of course, the file to be downloaded.
Then, each time a user will visit the page, you can :
- Check if he has already downloaded the file
- If no, redirect it to the real path of file
- If yes, display 403 message.
When a user download a file, update the DB, generate or copy the file to a new name, you play with headers, and delete file upon download or after a small timeout.

Security for Flat File CMS (PAAS)

I am building a flat file cms that uses php files. Users will be able to rename files using an input field and the old and new file paths will be sent via ajax to the server where I test for security. I realize this could be done easier with regex or even OR operators. I took the OR operators out so that the strings would not be too long for this post. And as for regex, I'd like more control over the errors I send back to the client.
The CMS itself is much like a PAAS that resides in directory above all of the individual site folders that each user will have. My goal is to keep users from injecting code that might interfere with other (adjacent) user folders or the cms itself in the parent directory above.
I have not parsed the path's yet for validity. I am just trying to get an idea of how a malicious user might be able to take advantage of what I have written so far.
<?php
$old_path = $_POST['file'].'.php'; // path/to/file.php
$new_path = $_POST['new_file'].'.php'; // path/to/newfile.php';
if(strstr($new_path,"<?")){
echo "Sorry, path cannot contain script tags";
}elseif(strstr($new_path,"?>")){
echo "Sorry, path cannot contain script tags";
}elseif (strstr($new_path,">")){
echo "Sorry, path cannot contain script tags";
}elseif (strstr($new_path,"<")){
echo "Sorry, path cannot contain script tags";
}elseif($new_path[0]==="." OR $new_path[0]==='/' OR $new_path[0]==='\\'){
echo 'Sorry first character of path cannot be a period or slash';
}else{
//this is set when the user logs in based on details in a database
$users_dedicated_directory = $_SESSION['userfolder'].'/';
// add the users folder when renaming just for more control
$old_path = $users_dedicated_directory.$old_path;
// add the users folder when renaming just for more control
$new_path = $users_dedicated_directory.$old_path;
rename($old_path,$new_path);
//trim the users folder name. Send it back to the client
echo explode($users_dedicated_directory,$new_path);
}
?>
if new_path is something like a/../../path/to/one/of/you/cms/core/file.php if think a malicious user could overwrite some files of your CMS.
You'll have to remove write permissions for the web server to nay of your CMS files to prevent that.

PHP Script is not Properly Uploading Images

I'm working on a small, user-maintained online store, and am trying to allow my end user (the store administrator) to upload graphics for products. When I run this script, however, it doesn't actually store the image. I built this script from various tips here and a tutorial, and have gotten everything but the image upload portion to work.
// Set the image target directory here
$target = "itemImages/";
$target = $target . basename($_FILES["image"]["name"]);
// Variables get POSTed here - just tack new ones on at the end.
// Various POSTs omitted for brevity
$pic=($_FILES["image"]["name"]);
// Places the picture in the folder
if(move_uploaded_file($_FILES["image"]['tmp_name'], "itemImages/"))
{
echo "The file " . basename($_FILES['uploadedfile']["name"]) . " has been uploaded.<br />";
}else {
echo "There was an issue adding this item. Please try again.<br />";
}
// Writes variables to the database
mysql_query("INSERT INTO tbl_item (itemNAME,itemDESC,itemCOST,itemHCOL,itemHSIZ,itemIMG)
VALUES ('$itemName','$itemDesc','$itemCost','$hasColor','$hasSize','$pic')");
mysql_close($con);
?>
Any help, tips, advice, insight, etc. would be very much appreciated.
move_uploaded_files requires a filename as its target. It does not blindly move to a directory, so
move_uploaded_files($_FILES..., 'somedir/somefile.txt');
works, but
move_uploaded_file($_FILES..., 'somedir/');
will not.
Plus, note that your database operation is vulnerable to SQL injection attacks. You're blindly inserting the uploaded file's remote name (['name'] via $pic), and that name is fully under the remote user's control.
Make sure the itemImages folder has write permission by the user your web server (e.g. Apache) is running as (e.g. www-data)
make sure the .php file and the folder you are writing to have the same "owner". Or try setting permissions on the itemImages folder to 777 (This is not recommended, just a debug tactic)

Document Sharing App With MVC

I have been assigned a new task by a client, a document sharing application to be made using the MVC design pattern. Here are the requirements:
Uploads and downloads files with a browser
Store the document in db if that are more secure documents else store on the directory with options of password enabled or accessible without password
Every user will be have own document catalog / workspace from where he can be able to share documents with other users as well. and public shared area to share and upload files
Super admin will be able monitor the file upload logging for monitoring purpose.
I have rough idea but I would really like to know your thoughts about above points especially what is in bold up there.
The third point above is most important and I am not sure where to start from and how to go about logging the uploads.
I am basically asking for implementation details about the third and fourth points.
Here is how I implement this with CakePHP and it works nicely. First, I make sure my app code sits above the public html directory so that it is not exposed to the web. So basically, the only files the users have direct access to is the index.php file, the css/js and image files.
Then, I update my file management model to save and delete files on the fly:
function beforeSave() {
extract($this->data['Upload']['file']);
if(isset($name) and !empty($name)) {
$filename = time().'-'.$name;
if ($size && !$error) {
move_uploaded_file($tmp_name, APP . 'media/files/' . $filename);
$this->data['Upload']['file'] = $filename;
$this->data['Upload']['name'] = $name;
$this->data['Upload']['file_type'] = $type;
}
} else {
// remove the photo so it is not updated
unset($this->data['Upload']['file']);
}
return parent::beforeSave();
}
function beforeDelete() {
$data = $this->read(null, $this->id);
if( is_file( APP . 'media/files/' . $data['Upload']['file'])) {
unlink(APP . 'media/files/' . $data['Upload']['file']);
}
return true;
}
This will manage the file upload and put all of the naming information into the database. And since the app/media/files directory is not accessible to the web, I do not have to protect the directory. It means that no matter what file the user wants, they have to access it from the website.
Then, all you have to do is make sure the model table has a "shareable" flag for the user to indicate that the file is accessible to the world, and then anyone can see the file and download it.

Categories