Prevent direct access to php file

I have caller.php that calls a second file get.php with some parameters (note get.php is not a runtime include, but called only when a user clicks a button), i.e.,

file=get.php?doc=../../abc.mp3

Both caller.php and get.php are in publicly accessible folders. abc.mp3 resides in a level higher than public and not open.

I want get.php to work ONLY when called from caller.php. If get.php called directly from the browser it should result in an error message.

I don’t want to use referrer checks if possible. Also, not looking for foolproof method, but something that is reasonably secure or will require a few steps each time to break.

I have considered passing a $secretkey from caller.php to get.php but anything I pass can be seen in the view source or headers? Also, session variables don’t work well I think as I don’t want user to go to caller.php first and then right after do a direct call to get.php because session key is set as that will trick get.php into working…

Is this possible? Any takers?

You could use a secret key that changes whenever a call is made based on a relatively arbitrary value.
e.g. set a cookie to a unique value (timestamp) before making the call, and pass a hash of the cookie value in the url, then clear the cookie once the response has been received. On the server side, the PHP script tests the cookie value and matches it against the hash in the URL, and rejects if the two don’t match

Why are you using get.php at all? Could caller.php not obtain the data directly?

@Mark
If there was a way without a cookie, that would be great. But if I follow you, you’re saying:

  1. set a cookie from within caller.php,
  2. md5/crypt it with some key,
  3. pass hash to get.php,
  4. decrypt hash in get.php with key,
  5. compare timestamp in browser’s cookie to timestamp from hash to ensure the same, else die
  6. compare current time() to timestamp received AND clear cookie,
  7. if > x seconds, deny access…
  8. If get.php called directly without hash or with old hash, it would deny.

Did I get this right?

@Silver
I am actually embedding a flash player in a page using code similar to that below. get.php basically fopens and freads the requested mp3 file and sends to the user. If I do this inside caller.php (in the “file=” part below) the fopen and fread gets executed while the page loads, taking really long to load and breaking the player…not sure why, but it works fine if “file=” points to another php file (get.php) that streams the mp3.



<?php
$fileonly=$_GET['param'];
$fileonly=str_ireplace("..","",$fileonly);
$fileonly=str_ireplace("/","",$fileonly);
$fileonly=str_ireplace(" ","_",$fileonly);
?>
<embed
src="mediaplayer.swf"
width="640"
height="480"
allowscriptaccess="always"
allowfullscreen="true"
autostart="true"
flashvars="file=get.php?param=<?php echo $fileonly; ?>&showstop=true&autostart=true&bufferlength=3"
/>

get.php has the following in it:


<?php
$fileonly=$_GET['param'];
$file="../music/".$fileonly;

header('Cache-Control: no-cache');
header('Cache-Control: no-store');
header('Pragma: no-cache');
header('Content-Type: audio/x-mp3');
header('Content-Length: ' . filesize($file));

$fh = fopen($file,"rb");
while (!feof($fh)) { print(fread($fh, filesize($file))); }
fclose($fh);
?>

You can easily store these files below your root directory which would make them completely inaccessable to the public.

Then use the script you have in the bottom to dynamically deliver the file.

Basically, your should look something like this:

/home/mysite/public_html/

Store files in:

/home/mysite/files/

@jestep

Are you referring to get.php that is called by caller.php?

I tried doing this, but caller.php (which is in public_html) couldn’t call get.php which was residing above the publicly accessible directory. Is there something that needs to be enabled/configured on the server that would allow caller.php to run get.php which is in a non-public folder?

I have access to the server so can make changes if required, hopefully not too daunting.

Just do this:

In caller.php

definet('IN_CALLER', 1);

In get.php

if (!defined('IN_CALLER')) { die(); }

However, the most secure is just to put everything outside of the webroot.

Edit: or are you invoking get.php via ajax? (on caller.php page)? If so, this won’t work…

No, this isn’t Ajax. Just a regular call to another php file.

Can I call get.php if it’s oustide the webroot? That would be ideal as no one (except my caller.php script of course) would be able to call it directly.

If by call you mean require/include, then yes, as long as you pass it the correct path ofcourse.

Is this what you mean? (No cookies needed.)

caller.php

<?php
$file_only = basename($_GET['param']);
$file_only = str_replace(" ", "_", $file_only);
// These don't really matter since you're just reading from one folder and you don't check extensions or anything, but I like to do it in case
$file_only = str_replace("\\0", "", $file_only); // Poison null byte
$file_only = str_replace(":", "", $file_only); // NTFS ADS
// Key
$shared_key = "something";
$timestamp = time();
$hash = hash_func_hmac($file_only, "$timestamp-$shared_key");
$url = printf("get.php?param=&#37;s&t=%d&key=%s", urlencode($file_only), $timestamp, $hash);
?>
<embed
src="mediaplayer.swf"
width="640"
height="480"
allowscriptaccess="always"
allowfullscreen="true"
autostart="true"
flashvars="file=<?php echo htmlspecialchars(urlencode($url)) ?>&amp;showstop=true&amp;autostart=true&amp;bufferlength=3"
/>

get.php

<?php
$file_only = $_GET['param'];
$shared_key = "something";
$in_hash = $_GET['key'];
$in_timestamp = intval($_GET['t']);
$in_hash = $_GET['key'];
$expected_hash = hash_func_hmac($file_only, "$in_timestamp-$shared_key");
if ($expected_hash != $in_hash || $in_timestamp < time() - 60 * 15) { // You could do the timestamp check before computing the hash
    header("HTTP/1.1 404 Not Found");
    exit;
}
// I would still do a security check again
// And then load the file
//

[QUOTE][/QUOTE]

@premiumscripts

No, I didn’t mean include get.php. I meant invoking (calling) get.php from caller.php when the user requests the file…see flash embed code that calls get.php to retrieve mp3 file.

@sk89q

Interesting, except that after calling caller.php, the user could directly call get.php (with the same parameters that were passed) and because the hash would still be valid (within 15 minutes), it would send back the file. So, users can call get.php directly to obtain the file…I want to block any direct calls to get.php.

Uh, define “direct call.” How are you going to play a sound file in Flash if your browser never sends a call to get.php? Do you mean that you only want Flash Player to be able to play it? That means that you will have to encrypt the sound file and not use Flash (or use Flash Media Server), because at the server, there is no sure way to tell whether Flash Player was the program issuing the request or not.

To prevent a PHP file being called directly and only allow its use if included in another PHP file just add the following code to the top of it.

$file = basename(__FILE__);
if(eregi($file,$_SERVER['REQUEST_URI']))
   {die('This file cannot be accessed directly!');}

@felgall
I’m not including the file, but it gets called by the flashplayer, so this check won’t do (as the call from get.php is also a separate call as far as the server is concerned)

@sk89q
I understand what you’re saying, I guess for php and the server there is no difference between the flash player in caller.php calling get.php and an evil visitor just typing the get.php directly into the browser.

I’m still lost…I just don’t want others to call get.php and my server just starts sending back the mp3 file that I’m trying to protect, else why even bother…

So what you need is a session established by the page that is calling the code and to check in the get.php that the same session still exists. Then if it is called from somewhere other than your page then the session will not exist.

Well, anyone knowledgable can easily download the MP3 file if you do it this way. Intercepting files served via HTTP is ridiculously easy.

Thanks for the responses. This is what I’ve been able to do so far, but stuck…

  1. Multiple songs (for playlist) sent to caller.php
  2. Upon load, caller.php sets cookie in browser (w/ a known hash value)
  3. caller.php calls get.php?param=song1.mp3
  4. get.php checks to see if cookie set w/ known hash value
  5. if cookie set, stream mp3 file and delete cookie, else die

I know this isn’t very secure, but at least dissuades the casual browser.
The problem I’m having is when the flash player in caller.php completes playing song1.mp3 and proceeds to song2.mp3. At this stage, in the flash embed code, it’s just making another call to get.php?param=song2.mp3 without setting a cookie again (because it’s not loading the file caller.php again which would’ve set the cookie). As a result, get.php can’t find the cookie (because it got deleted after streaming song1.mp3) and it rejects the call.

Any ideas on how to set the cookie again when the flash player makes the second or third or fourth calls…? I feel I’m almost there, but not quite!

Note: Same problem faced even if I use session variables instead of cookies

PHP-Nuke (original cms system)

used to use something like

<?
if (!eregi("modules.php", $_SERVER['PHP_SELF'])) {
    die ("You can't access this file directly...");
}
?>

where modules.php was the name of the file calling the other files that they didn’t want displayed directly.

If by call you mean require/include, then yes, as long as you pass it the correct path ofcourse.

What you want to do bakhlawa is impossible. You can not “protect” anything you provide access for on the internet from the fact that people can download it.

By default, the second you put it on the internet you have already lost.

You can only make it harder for them to manage to save the content.

However in this case, there is so many ways to “download” the content that I would not even care about adding protection to it. Instead I would just make certain that the download solution could not be abused to fetch any other files.

As you mentioned you have issues regarding the cookie, to solve that you need to rewrite the flash application, telling it to connect to the file for each song.

Though you do NOT know what connect to caller.php, it can be the flash script, but it dont need to be. Hence, the current protection is not even worth the time it took to implement it.