/    Sign up×
Community /Pin to ProfileBookmark

.htaccess to protect images in directory

I am trying to protect images so only the server and php scripts can display images and not let the user type in the absolute path to the browser to get the image.

For example, the user will be denied if they type in
[url]http://www.domain.com/images/image.png[/url]

But the script can access the image and display it on the page by using

<img src = “images/image.png”>

I found this online which reads,

The best way of handling file uploads securely is rather than giving writable permissions to users, is to allow the writable permission to apache itself. In this way the apache server has writable permission rather than the user. Just chown the writable folder to apache or nobody and assign 770 permission.

In this way the public has no access to read or write or execute permissions in the uploads folder. You will notice that apache has rwx and so as the owner. You can safely place the upload folder inside www folder without any concern.

chown -R apache uploads
chmod -R 770 uploads

If anybody tries to access the uploads folder, through URL you will see forbidden. Because apache is the grou owner you will have no problem in displaying the images or photos to the browser.

<img src=”uploads/file02929.gif”>

Now this does work by blocking the user from typing in the path to the image. However, my scripts cannot view the image either. Is there a way to make this work to allow access to the image through my php script?

to post a comment
PHP

27 Comments(s)

Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — You're confusing "script access to images" and "browser access to images" by separating them. The server has no way to differentiate these types of requests because when a php script "accesses an image" in the manner you're referring to, it simply outputs an <img> tag on the page. Either way the browser makes the exact same request for the image either using the url in the img tags href attribute or the url in the browsers address bar.

To prevent hotlinking of your images you can add a referrer check to ensure the users browser sends an address that originates from your site with the request for each image. Note that this value is both easily spoofed and still wont prevent users from typing the images url into the address bar if they are coming directly from a page on your site.

http://tools.dynamicdrive.com/userban/

There are other options but they are more complex and require moving the images outside the webroot and changing all your image hrefs to point to another script that allows access to the images using either short lived access keys or cookies/sessions.
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — Thank you for your reply.

You wrote "There are other options but they are more complex and require moving the images outside the webroot and changing all your image hrefs to point to another script that allows access to the images using either short lived access keys or cookies/sessions. "

This what I am looking to do as I am worried about security issues because the upload file is public with permissions set to 777 to allow the writing of files to it! So I want to store the images and pdf outside of root.

If I place all the images and pdf outside root in a folder called subscription, how do I write a tag like this to display them and link to a pdf also store outside root?

<a href='home/xxx/subscription/name.pdf'><img src='home/xxx/subscription/image1.png'>My Image </a>

I have multiple image/links that will need to be displayed on the page.

Can I use headers to display the images and pdf links? I don't understand how to do this if so. Could you please provide an example?

I'm sorry, I'm just lost at how to do this. I seems it should be simple.
Copy linkTweet thisAlerts:
@SyCoMar 24.2009 — When the image is uploaded store it in the non web accessible folder and store it's information in a database. I rename the file to the id of the row. Store the original name and the path to the file and if you're allowing multple file types store the mime type too.

You can call the image like this
[code=html]<img src="/path/to/image_display.php?imgid=123/imagename.jpg" />[/code]
Where image_display.php is the script that retrieves the info about the image and displays is

The extra '/imagename.jpg' bit is a fix for browsers freaking when the image has a .php extension. Right click 'save as' will save as the correct image name then as well. You can store the original image name on upload and add it to the src when drawing the image.

[code=php]
<?
//image_display.php

/*
Query the database using on the GET id (protect from SQL injection) and retrieve the info you need to build the image.
Here's a simple jpg example but using a switch() you can retrieve the mime type from the database and select the appropriate header().
is_numeric() is used here as a very simple validation on the id.
*/

header('Content-Type: image/jpeg');
$myFile='/subscription/'.(is_numeric($_GET['imgid'])?$_GET['imgid']:'error').'.jpg';//or get path info from database.
$fh = fopen($myFile, 'r');
$size=filesize($myFile);
$theData = fread($fh, $size);
echo $theData;[/code]
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — Once a user authentication system is in place you can write a script that checks if the user is authorized to download files.

file_guard.php
[code=php]
if( [user is not authorized to view protected files] ) {
header('HTTP/1.1 403 Forbidden');
exit;
}

$filepath = [ protected files base directory ].rawurldecode($_GET['name']);

if( !file_exists($filepath) ) {
header('HTTP/1.1 404 Not Found');
exit;
}

// set an appropriate content-type for the requested file
header('Content-Type: image/jpeg');

fpassthru($filepath);
[/code]


You may then reference protected files thru the script like so:
[code=php]
echo '<img src="file_guard.php?name=', rawurlencode([ protected file name ]), '" />';
[/code]
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — I should also mention that this script poses some security risk as it allows access to files outsite the webroot. Make sure you do not allow the script to provide access to files outside your protected files base directory if someone requests for example file_guard.php?name=............somefile

[code=php]
// if the path of the requested protected file does not start with the base path
if( strpos(realpath($filepath), $basepath) !== 0 ) {
header('HTTP/1.1 403 Forbidden');
exit;
}
[/code]


Note also that your basepath must be absolute rather than relative for this check to work.
Copy linkTweet thisAlerts:
@SyCoMar 24.2009 — I should also mention that this script poses some security risk as it allows access to files outsite the webroot. Make sure you do not allow the script to provide access to files outside your protected files base directory if someone requests for example file_guard.php?name=............somefile

[/QUOTE]


Good point to bring up. Saving the mime and using that to build the file name prevents this risk too. if you add the extension based on the filename suchs as
[code=php]$file'.jpg' [/code]
then you can't access anything other than the file types you allow.


.
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — I greatly appreciate everyone's help.

I do have further questions though....

I need to access both pdf and png files in the 'above the root' directory. All are stored in seperate directories such as 2009-03-05 may have Page-01.png Page-01.pdf Page-02.png Page-02.pdf etc. Another folder in the same directory maybe named 2009-03-06 with the same file names.

What I need to do is pass the folder name that I need the files from and then display them on a web page.

For example, I need to access folder 2009-03-05 in the above the root folder called 'subscription' and display ALL the images in it as a link to the pdf in the same folder like this....

<a href='Page-01.pdf'><img src='Page-01.png'></a>

<a href='Page-02.pdf'><img src='Page-02.png'></a>

I do not understand how to do this because you can't have multiple headers in a file, can you?

Is there an example I can look at?
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — 
I need to access both pdf and png files in the 'above the root' directory. All are stored in seperate directories such as 2009-03-05 may have Page-01.png Page-01.pdf Page-02.png Page-02.pdf etc. Another folder in the same directory maybe named 2009-03-06 with the same file names.
[/QUOTE]


That's fine. Just place the directories and files into the base directory. And reference them in following manner:

[code=php]
<a href='file_guard.php?name=2009-03-05/Page-01.pdf'><img src='file_guard.php?name=2009-03-05/Page-01.png'></a>
<a href='file_guard.php?name=2009-03-06/Page-02.pdf'><img src='file_guard.php?name=2009-03-06/Page-02.png'></a>
[/code]



I do not understand how to do this because you can't have multiple headers in a file, can you?
[/QUOTE]


It seems that your still thinking of a page as single request. The only issue your going to run into with headers is that the content-type header returned by the file_gurard script will not be the usual text/html as it is by default because this script needs to respond to requests for images and pdf files rather that web pages. You'll have to get the script to set the appropriate header based on either the requested files extension, a previously stored value (as SyCo suggested) or by scanning the file. To clear up any further confusion regarding headers I suggest you install an addon for firefox called "live http headers" that will allow you to open a side bar in firefox that shows both request and response headers of each request made by the browser in real time.

https://addons.mozilla.org/en-US/firefox/addon/3829
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — I don't know what is wrong..... I've done exactly as you have mentioned but I am still not able to view the image.

I have created file_guard.php
[CODE]
$filepath = "home/username/subscribe/test/2009-03-05".rawurldecode($_GET['name']);

if( !file_exists($filepath) ) {
header('HTTP/1.1 404 Not Found');
exit;
}

// set an appropriate content-type for the requested file
header('Content-Type: image/png');

fpassthru($filepath);
[/CODE]



I have stored this file in the test directory of the above path

Back on the public side, I created a file called file_guard_test.php.

I've tried the following and neither display the image

[CODE]
<?php

echo '<img src="file_guard.php?name=', rawurlencode('Page-004.png'), '" />';

echo "<br>";

echo '<img src="home/username/subscribe/test/file_guard.php?name=', rawurlencode('Page-004.png'), '" />';

?>
[/CODE]


What am I doing wrong?
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — If you put the request into the browsers address bar rather than embedded into a page then you'll be able to see the content returned by the script so you can debug as usual.

At first glance... your filepath is invalid because you didn't follow the base path with a trailing slash so you end up with: home/username/subscribe/test/2009-03-05Page-004.png

The script should be returning a 404 response to your example request.
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — I feel like I am from another planet or something.

What request do I put in the browser to debug with?

Also, is this whole file_guard.php suppose to be in the above the root directory then included by using an include in the public php to display the images to the webpage?

What is it that I am suppose to put on the public side?
Copy linkTweet thisAlerts:
@SyCoMar 24.2009 — In the script
[code=php]echo '<img src="file_guard.php?name=', rawurlencode('Page-004.png'), '" />'; [/code]

In the browser
[code=php]http://www.mysite.com/file_guard.php?name=123.jpg[/code]

The file guard is web browseable (as shown above) that file determines the path to your file, stored outside the web browseable area. There are lots of ways to do this. pass an id and look up a stored path or pass the filename to a predefined storage area. I only pass an ID to the script rather than the name. As it opens one less security issue that way, but both can be made equally secure.
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — Yes I plan to pass a variable or something other than a filename, but I am just trying to hard to get this whole thing to work so I'm just using the filename till then.

So, you are telling me that file_guard.php needs to go in my public directory in the same file where my script is stored that I use to display the images, correct? Then in my script that I want to get the images for, I use the <img src tags? Is this right?


I have file_guard on the protected directory and I thought I had to include it somewhere.
Copy linkTweet thisAlerts:
@SyCoMar 24.2009 — file_guard.php can be anywhere in the browseable area and is called like an image so can be anywhere relative to the displaying script. Treat it like an image or pdf and use it the same way. It in turn will get the image from the non browseable area.
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — 
I feel like I am from another planet or something.
[/QUOTE]


That is due to working with a script that returns raw data rather than text like most php scripts. Read over the live headers of a few web pages that contain only a few images and you'll see how the browser uses each series of requests to build each web page.

The file_guard script must be public in order to respond to requests. Although you could include it from another public script, there is no reason to do so and it would just add another level of confusion.

All that's going on here is that you needed an opportunity to restrict access to certain files. Normally the browser requests the files directly but instead we're having it request the files through a script. Through this script you have the opportunity to run some code that ensures the user is authorized to download each file before sending it along.
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — Well, I'm still doing something wrong... I think it is now the path to my directory above the root.

This is what I am using....

$filepath = "home/myAdminUsernameHere/subscribe/graitiot/2009-03-05/".rawurldecode($_GET['name']);



I have the directory named subscribe in the same directory where Public is. It is also along side other directories called Pictures, Examples, Documents, Desktop, etc.

Is this the right path to use?
Copy linkTweet thisAlerts:
@SyCoMar 24.2009 — Your missing a leading slash to make the path absolute and it think it should be

$filepath = "/subscribe/graitiot/2009-03-05/".rawurldecode($_GET['name']);

But ti does depend on how the server is set up.

You'll learn more about how to call file if you save this to a file and examine the echoed lines describing how the files are called.

[code=php]<?
echo $_SERVER['DOCUMENT_ROOT'];
echo '<br />';
echo $_SERVER['SCRIPT_FILENAME'];
echo '<br />';
echo $_SERVER['SCRIPT_NAME'];[/code]


then read up on what these variables are here

http://us3.php.net/reserved.variables


If you're just trying to get this to work first, use a simple name for the file and skip the encoding. Debugging means breaking down the script to it simplest version that works then building it up till you find the problem. Remove anything unnecessary that may case a problem.
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — Is this the right path to use? [/QUOTE]

nope ? now I see your also missing a leading slash.

Forget about your main script for the time being. Just ensure file_guard is working correctly by calling it directly. Type this into your address bar:

http://domain/file_guard.php?name=testfile.png

Make sure testfile.png has been place into your protected directory. And see what the script does. If it works you'll see the image... if not then either one of the error conditions will occur (drop some echo's in there so you don't just get a blank page) or you'll see the image. It's also possible that you'll see a bunch of garbage but that just means you've return a binary file without setting the proper content type.
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — I tried this and it still doesn't work...

$filepath = "/subscribe/graitiot/2009-03-05/".rawurldecode($_GET['name']);

In the filepath above, what is telling it to go above the root directory to get to the images?

This is why I was trying...

$filepath = "/home/myAdminUsername/subscribe/graitiot/2009-03-05/".rawurldecode($_GET['name']);


I just am not getting this.....
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — I typed in the browser as you asked and now I am prompted with a file download box to save or open file_guard.php!!!!

My god.............
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — I tried again and get this Warning:

<b>Warning</b>: fpassthru(): supplied argument is not a valid stream resource in <b>/home/myAdminUsername/Public/stuffnet/test/file_guard.php</b> on line <b>18</b><br />
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — If you're just going to do trial and error you may as well start banging your head against the wall right now.

Call the script directly and debug as usual... Maybe you forgot to close a bracket somewhere and the script isn't even compiling. Comment out the header('content-type... and passthru and replace them with an echo 'yay' Then the script is back to just a regular text based php script. If you see 'yay' it worked, if you see an error you can put in additional echos to see why, if you see a compile error you can fix it.
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — <b>Warning</b>: fpassthru(): supplied argument is not a valid stream resource[/QUOTE]

Swap the passthru call with readfile($filepath);
Copy linkTweet thisAlerts:
@SyCoMar 24.2009 — deleted

I'll leave you to it. I think 2 of us helping is just going to confuse.

And I fully agree, debug methodically!! Programming is logic not guess work.

Good luck.
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — Deleting
Copy linkTweet thisAlerts:
@our5authorMar 24.2009 — readfile($filepath); worked! YEAH!

I'll have to look up to see why fpassthru() doesn't, I guess.

Yes, I do understand about debugging, but generally I understand fully what I am writing and debugging.... this raw data thing is very new to me and I'm not quite sure what I am doing yet !!!

Regardless, thank you SO very much for helping me with this. I've tried for DAYS to get this to work!!! I truely appreciate your patience with me!
Copy linkTweet thisAlerts:
@tfk11Mar 24.2009 — Nice job getting it working. It's a little tricky writing php that doesn't return text but it's good to know how it's done as it often comes in very handy.
×

Success!

Help @our5 spread the word by sharing this article on Twitter...

Tweet This
Sign in
Forgot password?
Sign in with TwitchSign in with GithubCreate Account
about: ({
version: 0.1.9 BETA 4.29,
whats_new: community page,
up_next: more Davinci•003 tasks,
coming_soon: events calendar,
social: @webDeveloperHQ
});

legal: ({
terms: of use,
privacy: policy
});
changelog: (
version: 0.1.9,
notes: added community page

version: 0.1.8,
notes: added Davinci•003

version: 0.1.7,
notes: upvote answers to bounties

version: 0.1.6,
notes: article editor refresh
)...
recent_tips: (
tipper: @Yussuf4331,
tipped: article
amount: 1000 SATS,

tipper: @darkwebsites540,
tipped: article
amount: 10 SATS,

tipper: @Samric24,
tipped: article
amount: 1000 SATS,
)...