...

View Full Version : How to secure files on my public web server



flexillu
01-07-2011, 10:29 AM
I have an application (flex with adobe air) that my customers are going to use to download image files from my server..

At the moment the url's are stored in my public web root folder. the application navigates to the url and downloads the selected file.

eg. http://someserver/images/image001.jpg

What i would like to know is how to prevent my images being accessed by anyone who navigates to the above url, they could easily change the image to image002.jpg...

What is the best way to serve files to my customers. Would it be better for a php script to handle the download and then pass that file to my application?

Stooshie
01-07-2011, 03:39 PM
The best thing would be to store the files in a hidden, non-public folder somewhere on your server. Then write a web service in php that takes a request with an image id of some kind, verifies the user, copies the image file to a public folder and renames it to some random name (using a UUID is probably best) and then the web service returns the location of the new image file. You may need some cron job to sweep up images that were created more than, say, 15 minutes previous to the cron sweep.

If you are not sure about writing web services in php, take a look at nusoap (http://sourceforge.net/projects/nusoap/) It's fairly simple. Also, flex/air can connect to web services fairly simply and most of the as code is auto-generated for you.

ShaneC
01-07-2011, 08:45 PM
There's a few ways you can do this with images. Options!

WAY ONE (DENY ACCESS AND STREAM)

One way is to do something similar to what Sooshie suggests, though instead of copying the file I would advise streaming it.

First step would be setting up the method. What we're going to do is deny access, through apache, to certain types of files. So what we need is to turn the images you have from something like image.jpg into something like image.denyme.

Next, we need to make sure that we still know what each file name is and what image we're dealing with so that it can later be downloaded. That's where the database comes in.

You would want to set your database up having fields such as: ImageID, ImageFileName, ImageFileExtension, ImageOriginalFileName, ImageOriginalFileExtension, ImageFileSize, ImageMIMEType (The MIME Type will be used for sending a PHP header that tells the browser we're downloading a file).

Next, let's deny access to those files. Create an .htaccess file in the root of your web directory (typically in public_html/ or www/) and include the following code (remember .denyme is the file extension we're using in this example, but that extension can be anything).



<Files ~ "\.denyme$">
Order allow,deny
Deny from all
</Files>


Lastly, you would implement the PHP to actually stream the file:

Your code will want to look something similar to this:



<?php

// ASSUME $result IS A RETURNED ARRAY REPRESENTING YOUR DATABASE
// QUERTY SELECTING THE FILE THE USER WANTS TO DOWNLOAD

// Send Content information to tell browser we're downloading a file and of what size the file is
header( "Content-type: " . $result['ImageMIMEType'] );
header( "Content-Disposition: attachment; filename=\"" . $result['ImageOriginalFileName'] . "." . $result['ImageOriginalFileExtension'] . "\"" );
header( "Content-length: " . $result['ImageFileSize'] );

// ALTERNATIVELY, IF YOU DIDN'T STORE ImageFileSize IN THE DATABASE YOU CAN USE:
//header( "Content-length: " . filesize( "/path/to/images/" . $result['ImageFileName'] . "." . $result['ImageFileExtension'] ) );

// In case the image is updated we don't want to cache the file
header( "Cache-control: private" );

$fp = fopen( "/path/to/images/" . $result['ImageFileName'] . "." . $result['ImageFileExtension'], "r" );

// While we haven't reach the end of the file, print 2048 bytes of data from the file
while( !feof( $fp ) ){
$buffer = fread( $fp, 2048 );
echo( $buffer );
}

// Once we're done, close the file
fclose( $fp );

?>


WAY TWO (STORE IMAGE DATA IN THE DATABASE)

Depending on the size of your images, it may be more efficient (or convenient) to store your image data in the database.

This way your file isn't accessible from the URL, because it doesn't actually exist in file form until you create it.

First, set up a database so you can upload into it. Create fields, similar to before, ImageID, ImageOriginalFileName, ImageOriginalFileExtension, ImageMimeType, ImageCode.

* Important: Set the ImageCode database type to "BLOB" *

Now, we'll need to convert our images into the database. If you're using PhpMyAdmin you can upload into the BLOB element from directly in the "Insert Row" tab of panel.

If you want to create your own, however, it will look something similar to this:



<?php

// Uploading our image, let's get to the point where $image represents an image that has been uploaded
// For more information on this, Google File Uploads in PHP

$imgCode = chunk_split( base64_encode( $image ) );

$database = new MySQLi( "host", "username", "password", "db_name" );

// You can get the image data from the $image element using something like
// getimagesize() [http://php.net/manual/en/function.getimagesize.php]
$database->query( "INSERT INTO `myImagesTable` ( `ImageOriginalFileName, `ImageOriginalFileExtension, `ImageCode` ) VALUES ( '" . $imageOriginalFileName . "', '" . $imageOriginalFileExtension . "', '" . $imageMimeType . "', '" . $imgCode . "' )" );

?>


Now, as for outputting the image, it's easier than streaming this time around (if you want to just display it on a new page, that is). You can still choose to stream the file, if you wish.

The easiest way is to simply do:



<?php

// We're assuming again that $result is a result array from a database query selecting the image's row

header( 'Content-Type: ' . $result['ImageMimeType'] );
header( "Content-Disposition: attachment; filename=\"" . $result['ImageOriginalFileName'] . "." . $result['ImageOriginalFileExtension'] . "\"" );

echo( base64_decode( $result['ImageCode'] ) );

?>


SIDE NOTE: DYNAMICALLY ZIPPING IN PHP

If you are storing the images dynamically, you may want to look into generating ZIP files on the file (totally optional, just reduces download size). PHP has a very nifty API for that here: http://php.net/manual/en/book.zip.php.


I hope this long post helps you! If you have any questions just let me know.

flexillu
01-09-2011, 05:13 PM
Thanks both for your replies.

ShaneC..The images are now adobe illustrator files..".ai".

Would your first method still work for these files?

Also my flex app will be run in adobe air..not in a browser, so how would i send the file back to the app..the same way?

ShaneC
01-09-2011, 08:35 PM
Yes, this method will work for any file type. However, having said that, you should not store very large files in your database.

As for the Adobe Air question, honestly I haven't worked with that platform so I can't tell you if it will work or not. If, however, a direct file download will work then in theory this should work as well - since the downloads operate the same way.

Inigoesdr
01-10-2011, 01:32 AM
Also my flex app will be run in adobe air..not in a browser, so how would i send the file back to the app..the same way?

Yeah, you just need to open a URL request or use the URL as the source in your Image component. Keep in mind that you aren't preventing someone from accessing your images at all; just obfuscating where they are located. All they have to do is open up an HTTP proxy or packet sniffer and they will be able to see where the images are coming from. For that matter if you are only loading them in your AIR app there is no point in obfuscating them at all unless you are going to also require authentication to retrieve the file.

flexillu
01-10-2011, 10:57 AM
Yeah, you just need to open a URL request or use the URL as the source in your Image component. Keep in mind that you aren't preventing someone from accessing your images at all; just obfuscating where they are located. All they have to do is open up an HTTP proxy or packet sniffer and they will be able to see where the images are coming from. For that matter if you are only loading them in your AIR app there is no point in obfuscating them at all unless you are going to also require authentication to retrieve the file.

The files are of very high value, i understand that if someone really wants to get access to them they will find a way to do so. I am trying to prevent non techies deciding to download more than they are allowed.

I'm a little confused now i thought Stooshies and ShaneC's method would prevent people finding them on my server and downloading them?

I do need some kind of authentication for the retrieval of my files..so would this go in my php script? The users are required to log in to access the app, so could i also record who is downloading what?

Inigoesdr
01-10-2011, 03:05 PM
I'm a little confused now i thought Stooshies and ShaneC's method would prevent people finding them on my server and downloading them?
Only if you:

hen write a web service in php that takes a request with an image id of some kind, verifies the user


I do need some kind of authentication for the retrieval of my files..so would this go in my php script? The users are required to log in to access the app, so could i also record who is downloading what?

Yes, you would handle the authentication in PHP. If you just want to prevent people from traversing your images directory, turn off indexing so they can't see what files are in it by going to /images/.

# In your .htaccess
Options -Indexes

flexillu
01-11-2011, 12:09 PM
Yeah, you just need to open a URL request or use the URL as the source in your Image component.

Can you give me any more details on this part. The files are .ai files.

If i use the script above to output stream the file...

what exactly do i have to do in the flex app

at the moment i use a urlSteam to get the data bytes and write them to a new file and then append .ai onto it. Are you saying that i just need to URL request the php script tat ShanC provided?

ShaneC
01-11-2011, 05:53 PM
The method I provided for downloading the files is coupled with the Apache .htaccess change. If you deny access to a certain file type, like I specified in my post, it will prevent users from accessing that file.

The way you re-enable that access is by creating a PHP script, similar to the one I specified, which allows a user to download the script. Then, on that script, you institute whatever method you would like for authorizing a user to download the requested file.

Inigoesdr
01-11-2011, 07:20 PM
Can you give me any more details on this part.
No, because I have no idea how your application works in terms of displaying the image, authentication, etc. That is really a separate problem you should take to the Flex forum anyway.

The method I provided for downloading the files is coupled with the Apache .htaccess change. If you deny access to a certain file type, like I specified in my post, it will prevent users from accessing that file.
Changing the extensions of files is silly. Just stick them in a folder and deny access to that folder in the .htaccess:

order deny, allow
deny from all
Storing files in the database is also unnecessary and very inefficient.

ShaneC
01-11-2011, 07:51 PM
No, because I have no idea how your application works in terms of displaying the image, authentication, etc. That is really a separate problem you should take to the Flex forum anyway.

Changing the extensions of files is silly. Just stick them in a folder and deny access to that folder in the .htaccess:

order deny, allow
deny from all
Storing files in the database is also unnecessary and very inefficient.

First and foremost my suggestion was to deny access to the files in some form or fashion, the example I gave is by denying by extension - denying by directory works as well.

As for storing in the database, that was recommended based on the original request which was storing images, not AI files. Storing images in the database can be significantly more efficient as they can be accessed with O( log n ) efficiency, whereas if it is stored as a file you'll need an O( log n ) lookup for the file name in the database, plus an O( 1 ) access of the file. Obviously, though, if your files are large (which an AI file is likely to be), then putting them in a database isn't the best option.

As to "changing the extensions of files is silly", I have to respectfully disagree. If you're storing the original filename and extension in the database (which you'll need to stream the file anyway), then your file could be called PurpleGiraffe.awesome - so long as it can be accessed you'll end up streaming it to the user with the original file name and extension.

Inigoesdr
01-11-2011, 08:33 PM
As for storing in the database, that was recommended based on the original request which was storing images, not AI files. Storing images in the database can be significantly more efficient as they can be accessed with O( log n ) efficiency, whereas if it is stored as a file you'll need an O( log n ) lookup for the file name in the database, plus an O( 1 ) access of the file. Obviously, though, if your files are large (which an AI file is likely to be), then putting them in a database isn't the best option.
Regardless of the size of the file loading it directly from the disk is always going to be faster than querying the database, which also introduces several more potential issues. Most of the time, in my experience, you don't even need a reference to the file in the database unless you are going to make the filenames searchable.

As to "changing the extensions of files is silly", I have to respectfully disagree. If you're storing the original filename and extension in the database (which you'll need to stream the file anyway), then your file could be called PurpleGiraffe.awesome - so long as it can be accessed you'll end up streaming it to the user with the original file name and extension.
Yeah, you can name it whatever you want, but there is no reason to complicate it like that. Your point of denying access is a good solution, but renaming the files causes more frustration and work on your part when it's not necessary because you can block the whole folder(or place them outside of the document root) and keep your extensions(and sanity).

ShaneC
01-11-2011, 08:43 PM
I definitely respect the validity of your arguments, Inigoesdr. I suppose I'm just approaching it from the worst-case-scenario.


Regardless of the size of the file loading it directly from the disk is always going to be faster than querying the database, which also introduces several more potential issues. Most of the time, in my experience, you don't even need a reference to the file in the database unless you are going to make the filenames searchable.

My assumption, based on the original post, is that he's looking to dynamically index the files. That is, make a system by which he can grant someone access to a particular file (or set of files). In this case, a database is your best option. You're already going to need one for the authentication, and extending it to index what files you have will allow you to much more dynamically grant access to file (by adding permission entries).


Yeah, you can name it whatever you want, but there is no reason to complicate it like that. Your point of denying access is a good solution, but renaming the files causes more frustration and work on your part when it's not necessary because you can block the whole folder(or place them outside of the document root) and keep your extensions(and sanity).

In an ideal world then yes, every file uploaded has a unique file name. However when you upload a file with a name that already exists -- well now we have a problem. You'll need to rename the file regardless.

With the point I already made above, you're already going to have a database for authentication and, to prevent losing the identity of a file which has a duplicate file name, you're going to need to store the file names and their equivalents in the database. So, if you're implementing this type of architecture, and especially if a script is doing the uploading and renaming, the actual name and extension of the file is irrelevant.

flexillu
01-12-2011, 11:35 AM
I thought there was going to be standard way!

Could you confirm that i have the right idea here?

OK so i should make a call from my flex app to the phpscript.

The script will authenticate the user: Any advice on this? (Previosuly the user has logged in) via a different script.

Get the requested file copy it to a a public directory..or stream it as output. If i stream it..how will i handle that in the air app?

Get the app to download the copied file from its new temporary public directory..or get the stream. Write it to disk.

Then i would like to log the download of the file..could i store the user ID and the filename they downloaded in a table?

Inigoesdr
01-13-2011, 03:08 AM
I thought there was going to be standard way!
Believe it or not your requirements aren't all that comment. :thumbsup:

That being said, in any high-level programming language there is almost always several ways to accomplish something.

The script will authenticate the user: Any advice on this? (Previosuly the user has logged in) via a different script.
If they aren't logging in through the AIR app now, you are going to have to create a process to handle that. Again, there are multiple ways to handle this. You can store their username/password in the app and send them with any requests to the webserver, for instance, or create a persistent login system with cookies or tokens.

Get the requested file copy it to a a public directory..or stream it as output. If i stream it..how will i handle that in the air app?
The AIR app, and any other app is going to see it as a static file it's pulling from the server. As if you had typed in a direct URL. You would probably want to return a 403 if you get an error in your auth process, and capture that in the AIR app so you can Alert.show() an error message.

Then i would like to log the download of the file..could i store the user ID and the filename they downloaded in a table?

If you want to log that it was actually completed you could do something as simple as a URLRequest to a PHP script that would record the download. If you want to log just that a download was started you would do it in your script that handles the file streaming.

flexillu
01-13-2011, 11:41 AM
So processes for login and various things should be in different scripts, but on most i will check the username and password again. Does that sound ok?

Also where do most people put these php scripts on their server? Just in the web root? Do i not need to protect them from prying eyes?

Inigoesdr
01-13-2011, 04:34 PM
Also where do most people put these php scripts on their server? Just in the web root? Do i not need to protect them from prying eyes?

It doesn't really matter where you put it. Someone using your AIR app is going to be able to find out where the file is anyway just by observing their traffic. Just make sure your script is secure so that someone can't just go to the URL and view anything sensitive. Always assume they will know the URL to any given script.

flexillu
01-14-2011, 01:28 PM
Just make sure your script is secure so that someone can't just go to the URL and view anything sensitive. Always assume they will know the URL to any given script.

So the script needs to be web accessible so the VS app can access it remotely (in the web root).

If i block access to the script then the application will be blocked also?

Is there a way i'm missing to make the directory/script private so it can't be accessed through a URL but so that it can be accessed by the application?

Stooshie
01-14-2011, 04:04 PM
The script will authenticate the user: Any advice on this? (Previosuly the user has logged in) via a different script.


The way I do this, to make it secure is have a key created every time the user interacts.

When the user logs in, a key is created for that user on the server and also returned to the user.

When an interaction happens, the key on the server (held for that user) is checked against the key passed by the user for the current interaction. A new key is then created, held on the server for that user and returned to the user. This new key is then used for the next interaction. The process is repeated on every interaction. You could also have a timestamp on the server for that user and if the timestamp of the next interaction is more than X then get the user to log in again.

I hope that helps.

flexillu
01-14-2011, 04:17 PM
The way I do this, to make it secure is have a key created every time the user interacts.

When the user logs in, a key is created for that user on the server and also returned to the user.

When an interaction happens, the key on the server (held for that user) is checked against the key passed by the user for the current interaction. A new key is then created, held on the server for that user and returned to the user. This new key is then used for the next interaction. The process is repeated on every interaction. You could also have a timestamp on the server for that user and if the timestamp of the next interaction is more than X then get the user to log in again.

I hope that helps.

Yeah that does help, so is this sort of thing done in PHP? When you say "held on the server" is that what you mean, or is it some sort of Apache feature.

Without giving away the method you use to create your keys obviously is there anychance you can provide some example code?

Also going by what you said, is it pretty easy to log user activity by putting this timestamp and what they accessed into a log? Or again is logging a seperate feature in Apache?

Oh and are all these things like user key, username and password ok to be passed in the POST headers of a html request FROM the app to the script?

And by XML output TO the application?


Thanks for your help, sorry for all the questions!

Stooshie
01-14-2011, 04:29 PM
It would mean storing the key in a field on the database on the server (and using php to interact with that). Are you already using a db for login? If so create a field to hold the key.

The key I use is just a UUID (the php function is uniqid() (http://php.net/manual/en/function.uniqid.php)). As long as it is unique, fairly long and changes on each interaction.

flexillu
01-14-2011, 06:24 PM
Yeah currently using db for login so i'll just bang an extra field in.

What about recording which user is downloading what file? Is this something i have to record myself, or something apache can do?

I'd like to record the username, time/date of request and the file_name that they downloaded/started to download, would i just do that by inserting that data into a table?

Stooshie
01-17-2011, 03:44 PM
Inserting into a table would probably be the easiest way.

flexillu
01-26-2011, 11:20 PM
Struggling getting the file copied into the new directory...I haven't even thought about UUID yet. Is there any tutorials on this i really need help?

I'm getting an error saying that the destination file/path doesn't exist, but i thought the COPY function was supposed to create it if it didn't.

here's my code


<?php

Define( "DATABASE_SERVER", "localhost" );
Define( "DATABASE_USERNAME", "vsuser" );
Define( "DATABASE_PASSWORD", "Mal108" );
Define( "DATABASE_NAME", "vectorSketch" );
Define( "PORT", "3306" );



$fileName = $_POST['fileName'];

//connect to the database
$msql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
mysql_select_db( DATABASE_NAME );


$query = "SELECT * FROM productImages WHERE fileName ='$fileName'";

$Result = mysql_query( $query )
or die ("Query failed: " . mysql_error() . " Actual query: " . $query);

$productImage = mysql_fetch_array($Result);


copy ( ("/var/www".$productImage['filePath'].$productImage['fileName'].".ai") , ("/var/www/newtestdir".$productImage['filePath'].$productImage['fileName'].".ai") )


?>

any help at all would be great



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum