2

My webapp has a buch of modules. Each module has a 'main' php script which loads submodules based on a query sent to the main module:

//file: clientes.php

//check for valid user...
//import CSS and JS...

switch( $_GET["action"] )
{
    case "lista" :          require_once("clientes.lista.php"); break;
    case "listaDeudores" :  require_once("clientes.listaDeudores.php"); break;
    case "nuevo" :          require_once("clientes.nuevo.php"); break;
    case "detalles" :       require_once("clientes.detalles.php"); break;
    case "editar" :         require_once("clientes.editar.php"); break;         
    default : echo "<h1>Error</h1><p>El sitio ha encontrado un error.</p>";
} 

This main module deals with security and imports many resources all submodules need. The big problem shows up when a user asks for any of the submodules, bypassing all the security measures on the main module! My idea was to add a line on every submodule to test if it was being called directly and deny access or if its been called via another script, and continue. The least thing I would like to do is redo the security checking on every single file, since it does a bunch of query's to the database.

Does a php script know if its been called via a require_once() or a direct call ? I've been trying to implement some sort of $_SERVER['REQUEST_URI'] and $_SERVER['PHP_SELF'] pitfall but I was wondering if there was some sort of an elegant way of doing this.

alanboy
  • 369
  • 3
  • 15

11 Answers11

24

I was looking for a way to determine if a file have been included or called directly, all from within the file. At some point in my quest I passed through this thread. Checking various other threads on this and other sites and pages from the PHP manual I got enlightened and came up with this piece of code:

if ( basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"]) ) {
  echo "called directly";
}

else {
  echo "included/required"
}

In essence it compares if the name of the current file (the one that could be included) is the same as the file that is beeing executed.


EXPLANATION:

  • __FILE__ is a PHP magic constant that stores the full path and filename of the file, the beauty of it is that if the file has been included or required it still returns the full path and filename of such file (the included file).
    (Magic Constants Manual: http://php.net/manual/en/language.constants.predefined.php)

  • $_SERVER["SCRIPT_FILENAME"] returns the absolute pathname of the currently executing script. As when a file is included/required it's not executed (just included) it returns the path name of the (let's say) "parent" file (the one that includs the other file and the one that gets executed).

  • basename(string $path) is a function that returns the trailing name component of path, that in this case is the file name. You could also just compare the full path and filename, that would be indeed better, it isn't really neceseary to use this function but it feels cleaner this way, jajaj.
    (basename(): http://php.net/manual/en/function.basename.php)

I know it's a "bit" late to be answering the main question but I guessed that it could be useful to anyone who's on the same situation that I was and that also passes by.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
  • That's a good one. You don't need to defines where ever you require this file - cumbersome and if you forget - catastrophe. Also, we can test for basename($argv[0]) to cover for command line calls as well – rsmoorthy May 05 '12 at 19:07
  • So far this is the most attractive `DRY` implementation for me. Thanks! – jibiel Feb 26 '13 at 16:59
  • 1
    You shouldn't be using basename, many files have the same name but reside in different directories, otherwise this is a good solution, and more or less the same as I arrived at here https://gist.github.com/jthoburn/5302060 – runspired Apr 03 '13 at 16:24
  • but a calling script can set these values before including, can't it? e.g. $_SERVER['SCRIPT_NAME'] = 'the_included_script.php'; – wesamly Feb 13 '14 at 12:48
  • Thanks very much for the answer! This really put me in the direction I was needing. I was able to use this to include with my classes to be able to use the class with other languages and spit out JSON if need be. Here is one example I just did with a PHP email verification script https://github.com/DukeOfMarshall/PHP---JSON-Email-Verification/tree/master – The Duke Of Marshall שלום Mar 05 '14 at 16:29
13

One elegant way is putting all your files which should only be accessed via include outside the web directory.

Say your web directory is /foo/www/, make an include directory /foo/includes and set this in your include_path:

$root = '/foo';
$webroot = $root.'/www'; // in case you need it on day
$lib = $root.'/includes';
// this add your library at the end of the current include_path
set_include_path(get_include_path() . PATH_SEPARATOR . $lib); 

Then nobody will be able to access your libraries directly.

There's a lot of other things you could do (test a global variable is set, use only classes in libraries, etc) but this one is the most secure one. Every file which is not in your DocumentRoot cannot be accessed via an url,. But that does not mean PHP cannot get access to this file (check as well your open_basedir configuration if you have it not empty, to allow your include dir in it).

The only file you really need in your web directory is what we call the bootstrap (index.php), with a nice rewrite rule or a nice url managment you can limit all your requests on the application to this file, this will be a good starting point for security.

regilero
  • 29,806
  • 6
  • 60
  • 99
  • 1
    If at all possible and practical, this is the best way to go. +1 – Pekka Dec 28 '10 at 12:18
  • Have never done this before, and surprised I never thought of it. Thank you for teaching an old dog new tricks. – PseudoNinja Dec 28 '10 at 15:10
  • 1
    @OldCode101: no problemo, that's something we saw rarely on PHP projects as they're often stuck with a deployment with a FTP access directly in the DocumentRoot. – regilero Dec 28 '10 at 15:26
  • Uses of `$foo` subsequent to the declaration should also include the `$` variable prefix. – Core Xii Aug 30 '11 at 18:42
6

One popular method to make sure modules are not called directly is defining a constant in the main script, and checking for that constant in the module.

// index.php
define("LEGIT_REQUEST", true);

// in each module
if (!defined("LEGIT_REQUEST")) 
  die ("This module cannot be called directly.");
Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • Curious about the extra check. In which circumstances can `!defined("LEGIT_REQUEST")` not be enough? – Álvaro González Dec 28 '10 at 12:26
  • @Álvaro yeah, I thought the same when I wrote it already, it's redundant really - there is no conceivable case of setting the constant to `false`. Removed – Pekka Dec 28 '10 at 12:28
  • There are more future proof ways of accomplishing this: https://gist.github.com/jthoburn/5302060 – runspired Apr 03 '13 at 16:23
5

For the sake of completeness, the other possibility is to move such files to a directory that's not publicly available. However, some control panels used by hosting providers make this impossible. In such case, if you are using Apache you can place an .htaccess file inside the directory:

#
# Private directory
#
Order allow,deny
Deny from all
Álvaro González
  • 142,137
  • 41
  • 261
  • 360
4

A common technique is to add this to the main module (before the includes)

define('TEST', true);

and to add something like that at the first line of every submodule

if (!defined('TEST')) {
    die('Do not cheat.');
}
hangy
  • 10,765
  • 6
  • 43
  • 63
  • That is the most efficient way, because usually all includes are called from a central node, based on some variables or conditions. This is the minimum code for the server to parse, so go with this ! – Lucian Minea Nov 02 '16 at 12:19
3

An alternative to defining a constant and checking it is to simply put the files that index.php includes outside of the document root area. That way the user can't directly access them via your web server at all. This is also obviously the most secure way, in case your web server has a configuration error in future that eg. displays PHP files as plain text.

reko_t
  • 55,302
  • 10
  • 87
  • 77
1

A generic way that works without having to define a constant or use htaccess or use a specific directory structure or depend on the $_SERVER array that could theoretically be modified is to start each include-only (no direct access) file with this code:

<?php $inc = get_included_files(); if(basename(__FILE__) == basename($inc[0])) exit();
A. Genedy
  • 578
  • 4
  • 11
1

You can define('SOMETHING', null) in clientes.php and then check if (!defined('SOMETHING')) die; in the modules.

Core Xii
  • 6,270
  • 4
  • 31
  • 42
1

global.php

if(!defined("in_myscript"))
{
    die("Direct access forbidden.");
}

module.php

define("in_myscript", 1);
include("global.php");
Dejan Marjanović
  • 19,244
  • 7
  • 52
  • 66
0

Short and simple (for CLI):

if (__FILE__ == realpath($argv[0]))
    main();
kungfooman
  • 4,473
  • 1
  • 44
  • 33
0

As practice of habit I have a console class built to send messages, errors, etc. to console with FirePHP. Inside the Console class write() method I have a check to see if a $_REQUEST[debug] == 1, that way I'm not exposing errors to users if something pops up on production and they would have to know what the request variable is to access the debug information.

At the top of every file I add:

Console::debug('fileName.php is loaded.');

here is a snippit from it to give you the right idea:

class Console{

  public static function write($msg,$msg_type='info',$msg_label=''){
    if(isset($_REQUEST['debug']) && $_REQUEST['debug'] == 'PANCAKE!'){
      ob_start();
      switch($msg_type){
        case 'info':
          FB::info($msg, $msg_label);
          break;
        case 'debug':
          FB::info($msg, 'DEBUG')
          break;
          ...
      }
    }
  }

  public static function debug($msg){
    Console::write($msg, '');
  }
}
PseudoNinja
  • 2,846
  • 1
  • 27
  • 37