40

Is there a way to prevent a user viewing an file but still use it as included to another file in PHP?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Martti Laine
  • 12,655
  • 22
  • 68
  • 102

15 Answers15

53

If you use

define('APP_RAN'); 

in the file that includes it and then put

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

or alternatively

defined('APP_RAN') or die();

(which is easier to read)

in included files it would die if you access them directly.


It would probably be better to put all of your included files above your DocumentRoot though.

For example, if your index page is at

/my/server/domain/public_html

You should put the included files in

/my/server/domain/
Community
  • 1
  • 1
Tyler Carter
  • 60,743
  • 20
  • 130
  • 150
  • 2
    This is the best solution, it's part of basic website protection. – Daan Mar 07 '10 at 16:50
  • Which one, defining or the path? – Martti Laine Mar 07 '10 at 16:55
  • Both, even if you can use only one. The path is easier to implement, the define will take some time to be implemented. – Daan Mar 07 '10 at 17:03
  • `defined('APP_RAN') or die();` - agreed. Boilerplate should be as short as possible. – Iiridayn Jun 25 '19 at 06:16
  • This is not a good solution and is outdated. Global code should be avoided in favor of namespaced code for one, but really the question can be solved programmatically without worrying about setting constants in every file that may act as an entry point. `get_included_files()[0] === __FILE__` – Richard Tyler Miles Sep 14 '22 at 05:37
  • Also, PHP running in CLI is probably a more common use case for this which may or may not have anything to do with the web. – Richard Tyler Miles Sep 14 '22 at 05:40
15

My proposal:

<?php
if (__FILE__ == $_SERVER['SCRIPT_FILENAME']) {
    header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
    exit("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n<html><head>\r\n<title>404 Not Found</title>\r\n</head><body>\r\n<h1>Not Found</h1>\r\n<p>The requested URL " . $_SERVER['SCRIPT_NAME'] . " was not found on this server.</p>\r\n</body></html>");
}
else {
   // your code
}
?>

1.) it checks if it is called directly else it throws an error

2.) it outputs a 404 standard apache error page (please compare with your original 404 page or simple include that page) to add security through obscurity

3.) The else-part avoids partial execution while the file is uploaded to the live environment (PHP doesn't wait for the "?>"). You don't need it if your included file contains only one function / one class.

mgutt
  • 5,867
  • 2
  • 50
  • 77
13
if (!defined('FLAG_FROM_A_PARENT'))
// Works in all scenarios but I personally dislike this

if (__FILE__ == get_included_files()[0])
// Doesn't work with PHP prepend unless calling [1] instead.

if (__FILE__ == $_SERVER['DOCUMENT_ROOT'] . $_SERVER['SCRIPT_FILENAME'])
// May break on Windows due to mixed DIRECTORY_SEPARATOR

if (basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME']))
// Doesn't work with files with the same basename but different paths

if (realpath(__FILE__) == realpath($_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME']))
// Seems to do the trick
hichris123
  • 10,145
  • 15
  • 56
  • 70
tim
  • 2,530
  • 3
  • 26
  • 45
13

Do not use any global code in your files, only functions and methods. Then there will be no need to care about include vs. direct use.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user187291
  • 53,363
  • 19
  • 95
  • 127
  • 3
    Agreed; if anyone manages to guess the name of your include file that has only functions and classes, all they'd see is a blank page when trying to view it. If you're outputting html with an include (which I don't like to do) you can instead put it into a function, include the file, and call the function. – JAL Mar 07 '10 at 17:30
  • 1
    This is a good solution, but beware: if you leave your scripts in the public directory, remember to either a) make sure you don't have any syntax errors or b) turn off display errors, because otherwise they will still show up, even if the file only contains functions/classes. – Chris Middleton Dec 11 '14 at 03:01
  • I feel it should be pointed out that the technique is useful for more than just blocking direct access. Using the "needs external variable" technique, you can give a file different functionality when accessed directly. So, for example, you can easily pull in variables from post and run the primary function rather than just waiting for functions to be called and print a response, allowing you to turn it into an easy REST interface. – lilHar Nov 04 '15 at 17:07
9

Just store the file outside your web root.

Erik
  • 20,526
  • 8
  • 45
  • 76
  • Not really. From experience, some modules in some frameworks insist that your additional code be placed in specific locations, such as Laravel. – lilHar Nov 04 '15 at 17:10
  • 1
    @liljoshu Yes, and frameworks like Laravel store the majority of your files ... guess where, outside the web root. – Erik Nov 08 '15 at 01:58
  • 1
    @Erik yesterday If you have the luxury of working on a site that was designed properly or you had the benefit of designing the site yourself, which is not always the case, that may be the case. The last Laravel I worked with had nothing outside of web root, sadly. And even then, not all files are outside of web root. Sometimes you need to work in the web root either because situation forces it or that's how a framework functions. It's definitely _a_ good answer. Just not _the only_ good answer. – lilHar Nov 09 '15 at 06:07
  • Frameworks do not have any say in where I put my code. Anything that prevents basic security measures is to be avoided. – Félix Adriyel Gagnon-Grenier Nov 28 '18 at 15:46
7
if (__FILE__ == $_SERVER['DOCUMENT_ROOT'].$_SERVER['PHP_SELF']){
  die("Direct access forbidden");
}

It works regardless of where it is. (Cheers to @col-sharpnel for pointing out an incomplete yet good solution.)

moebius_eye
  • 574
  • 6
  • 14
  • -1 Do not rely on PHP_SELF as this is a security vulnerability to site.com/myfile.php/badstring.php – tim Jan 05 '14 at 17:26
  • 1
    I don't understand how this is a vulnerability. site.com/myfile.php/badstring.php simply results in a 404 error, in a simple install. – moebius_eye Jan 06 '14 at 20:01
  • php_self is set by the agent based on the request URI. /index.php/dummy_hijack.php will not resolve /index.php. $_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME'] does. Your example is safe in a way, but not accurate. users who use basename are in trouble using PHP_SELF though. if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) – tim Jan 07 '14 at 10:16
  • Okay. I get it now. Feel free to edit my post to add a warning. I don't know if I can explain it right myself. – moebius_eye Jan 07 '14 at 22:25
  • 1
    Seems like this solution would be fine for when the program can either be included or directly accessed and you need a way to detect which method is being used. – Drew Aug 06 '14 at 17:21
  • Yep. I've added a second answer which I think is better. Yet I got no upvotes: https://stackoverflow.com/questions/2397004/php-check-if-a-file-is-loaded-directly-instead-of-including/23722089#23722089 – moebius_eye Aug 07 '14 at 11:08
5

There's the get_included_files function. You could use it like so:

if ( empty(get_included_files()) ) die("Direct access forbidden");
moebius_eye
  • 574
  • 6
  • 14
  • 4
    Should be noted as of PHP5 this condition would never pass. See [this comment in the docs](http://php.net/manual/en/function.get-included-files.php#45451). The current file is always in the array, so the array always has at least 1 item ergo would never be empty. In PHP5+, one could try `if ( count(get_included_files()) === 1 ) die("Direct access forbidden");` instead. – notacouch Feb 02 '16 at 15:14
  • This will work; `get_included_files()[0] === __FILE__` – Richard Tyler Miles Sep 14 '22 at 05:29
2

Under Apache, this is easy: add a <Files> directive in your .htaccess to prevent access to it from a web browser. This doesn't apply to PHP includes, and it is a good practice to hide several files from browser access (though usually you'll try to put all the non-accessible files together in a single directory).

<Files="myprivatefile.php">
    deny from all
</Files>

Under a different web server, you can hide the file out of your document root, but under some cases (like if your scripts are open_basedir'd in a strict way), it won't work.

zneak
  • 134,922
  • 42
  • 253
  • 328
  • This is the more "standard" way in most business environments that use the LAMP stack (which is where you'll see PHP the most in the business world.) – lilHar Nov 04 '15 at 17:15
2

Check how many included files are there...

if(count(get_required_files()) < 2) { die(); }

Or how many minimum there should be rather than 2

HelpNeeder
  • 6,383
  • 24
  • 91
  • 155
1
if($argv[0] == basename(__FILE__))
    include_once('/path/to/file.php');
mechimdi
  • 318
  • 4
  • 6
1

I'd go for Chacha102's solution.

Additionally, as your question title says «how to check», you can also do this without having to define a variable by using

// secret.php is the name of this file.
if($_SERVER["SCRIPT_FILENAME"]=='secret.php') die();
Community
  • 1
  • 1
Daan
  • 1,879
  • 17
  • 18
  • 4
    This won't work as SCRIPT_FILENAME includes the complete path and not the filename only. – mgutt Oct 20 '11 at 17:49
1

There are many good answers in this thread - here's one that hasn't been mentioned yet.

You can name your included PHP files with an i in the extension, e.g. someincludedfile.phpi and then configure Apache so that it doesn't serve phpi files. Voila.

Cons:

  • (!) highly dependent on the Apache configuration (so you're vulnerable if someone neglects that line) -- and in such a event, the browser might get to see the whole PHP script in plaintext since it won't get passed off to PHP (really bad!)
  • it can be annoying to rename your included files

Pros:

  • makes it clear which files are to be included and which aren't in your code
  • you don't have to move your file hierarchy around

Personally, I would rather just move files into a directory outside the document root, but if you have a large legacy application, this could be quicker to change.

Link: http://www.ducea.com/2006/07/21/apache-tips-tricks-deny-access-to-certain-file-types/

Chris Middleton
  • 5,654
  • 5
  • 31
  • 68
1

If you want to stop it from ever being displayed when not included here's a more automated way.

if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) header("HTTP/1.0 404 Not Found");

This way it'll still work if you end up changing the file name or something.

ZeekDaGeek
  • 21
  • 4
0

Just to expand on the solutions with $_SERVER variables - below is a small .php file, and in the comments is a copy of a bash test I did on Ubuntu; the point being, that these variables can change quite a bit depending on the type of access, and whether symlinks are used (note also, in the code below I have to escape the '?\>' in the echo statement, else the syntax color breaks; remove the backslash if trying the code):

<?php

function report($label, $value) {
  printf ("%23s: %s\n", $label, $value);
}

report("DOCUMENT_ROOT.PHP_SELF",  $_SERVER['DOCUMENT_ROOT'].$_SERVER['PHP_SELF'] );
report("SCRIPT_FILENAME",         $_SERVER['SCRIPT_FILENAME'] );
report("__FILE__",                __FILE__ );
report("PHP_SAPI",                PHP_SAPI );

/*
# the test (bash):

home~$ mkdir /tmp/ptest
home~$ cd /tmp/ptest/
ptest$ ln -s /real/path/to/varcheck.php .
ptest$ echo '<? require_once "varcheck.php"; ?\>' > varcheckincl.php


# ... and in a separate terminal, run
# php (>5.4) cli server at same (/tmp/ptest) location:

ptest$ php-5.4.10 -S localhost:8000

# back to first terminal, the test - and output:

ptest$ php varcheck.php
 DOCUMENT_ROOT.PHP_SELF: varcheck.php
        SCRIPT_FILENAME: varcheck.php
               __FILE__: /real/path/to/varcheck.php
               PHP_SAPI: cli

ptest$ php -r 'require_once "varcheck.php";'
 DOCUMENT_ROOT.PHP_SELF: -
        SCRIPT_FILENAME:
               __FILE__: /real/path/to/varcheck.php
               PHP_SAPI: cli

ptest$ php varcheckincl.php
 DOCUMENT_ROOT.PHP_SELF: varcheckincl.php
        SCRIPT_FILENAME: varcheckincl.php
               __FILE__: /real/path/to/varcheck.php
               PHP_SAPI: cli

ptest$ wget http://localhost:8000/varcheck.php -q -O -
 DOCUMENT_ROOT.PHP_SELF: /tmp/ptest/varcheck.php
        SCRIPT_FILENAME: /tmp/ptest/varcheck.php
               __FILE__: /real/path/to/varcheck.php
               PHP_SAPI: cli-server

ptest$ wget http://localhost:8000/varcheckincl.php -q -O -
 DOCUMENT_ROOT.PHP_SELF: /tmp/ptest/varcheckincl.php
        SCRIPT_FILENAME: /tmp/ptest/varcheckincl.php
               __FILE__: /real/path/to/varcheck.php
               PHP_SAPI: cli-server
*/
?>
sdaau
  • 36,975
  • 46
  • 198
  • 278
0

Based on @HelpNeeder's post here. Will work regardless of directory or context.

// if we called this file directly, then we are using stdout
if (get_included_files()[0] === __FILE__) {

    /** @noinspection PhpUnhandledExceptionInspection */
    print json_encode($deploymentMatrix, JSON_THROW_ON_ERROR);

} else {

    return $deploymentMatrix;

}