PHP `require_once` includes wrong file - php

I have a development tree on a Linux Ubuntu 14.04-LTS machine like this, with three identical branches:
main -+-- leonardo --- project --- htdocs -+- panel --- index.php
| |
| +- config.php
|
+-- federico --- project --- htdocs -+- panel --- index.php
| |
| +- config.php
|
+-- carlo ------ project --- htdocs -+- panel --- index.php
| |
| +- config.php
..... (you get my drift).
There are neither soft links nor hard links. The config.php file is in svn-ignore and is different between all branches
There is an Apache server and there is a virtualHost for each developer, so I can see my development version at http://leonardo.project.local or Federico's at http://federico.project.local .
While investigating the current weirdness, the two files are these:
<?php // this is panel/index.php
echo "I am " . __FILE__ . "\n";
echo "I will include " . realpath('../config.php') . "\n";
require_once '../config.php';
<?php // this is config.php
echo "I am " . __FILE__ . "\n";
exit();
The expected output of course would be:
I am leonardo/project/htdocs/panel/index.php
I will include /var/www/main/leonardo/project/htdocs/config.php
I am leonardo/project/htdocs/config.php
But the actual output is:
I am leonardo/project/htdocs/panel/index.php
I will include /var/www/main/leonardo/project/htdocs/config.php
I am federico/project/htdocs/config.php
The additional weirdness is that
echo "I will include " . realpath('../config.php') . "\n";
require_once realpath('../config.php');
works.
TL;DR require_once and realpath disagree about where '../config.php' actually is.
The really strange thing is that I do not see how a script running in leonardo/project/htdocs/panel/ could know about federico/project/htdocs/config.php; it ought to go four directories up, then explore very many subdirectories.
I'm almost beginning to suspect that this could be something filesystem- or even kernel- related.
The filesystem is ext4, the kernel is 3.13.0-55-generic #92-Ubuntu SMP Sun Jun 14 18:32:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux. The machine is a virtual x64 on the latest VMware Workstation.
Checks
PHP's include_path only includes . and /usr/local/php5/pear.
as stated earlier, no files in the branch are symlinks, and the inode counts for all involved files indicate there are no cross links. The files are indeed different.
all files are really there, it's not a "last ditch include".
from command line, in leonardo...panel, I run "cat ../config.php" and I get my config.php, as expected. It is only from PHP that the wrong file gets included.
restarting Apache (just in case) availed nothing. I'll next try and reboot the whole VM, but to do that I need to freeze several services and it will take me a while.
everything was hunky dory up to yesterday (I wasn't here then). There were no system updates, no reboots, and not even remote logins in the last three days. Uptime is now eight days.
I'm an idiot: I can too know to the minute when this started happening by checking the integration test logs. Have asked for them, expecting them after lunch.

It's worth checking what your include_path is set to (this can be done using get_include_path).
require and include will behave differently given an absolute and relative path. When you use
require_once realpath('../config.php');
This is doing:
require_once '/var/www/main/leonardo/project/htdocs/config.php';
Which works as you'd expect.
The weirdness in the following:
require_once '../config.php';
occurs because PHP will check each entry in the include path for a matching file and return the first matching entry. Hence it's likely that the path to the federico config is being checked first.

Have you checked any potential opcode caches and their settings?
In the past I have had some issues there, eg not detecting changed files.
Specifically, this situation can and will happen if opcache.use_cwd setting is set to zero.
opcache.use_cwd boolean
If enabled, OPcache appends the current working directory to the script key, thereby eliminating possible collisions between files with
the same base name. Disabling this directive improves performance,
but may break existing applications.
If this happens, then the first user or phpunit script accessing a file of a given name in a different directory (e.g. leonardo/config.php vs federico/config.php) will "prime" the cache with that file. The file system functions such as realpath will not be affected and will continue working. References using absolute paths will continue working. References with relative paths will be broken in a quite insidious way.
For until you have only one person working, that person has the cache primed for his needs and will notice nothing. Then you come back to work, and you start loading his files.
On a side note, the setting might cause unintentional information disclosure, because the setting is system wide. So you know that your ISP has a broken use_cwd, you know that another site includes '../inc/credit_cards.php', you prepare the same path in your site and include a file with the same name. get_defined_vars() might have you pwn the other site's login or database system. (Haven't checked, but given what happened, don't see why not).
UPDATE
I checked with the above configuration on PHP5.6 and an old backed up VM (we have time-machine snapshots of the last five years :-) ). I was indeed able to read all defined global variables in a different virtual host to which I had no access, overriding authentication.
This problem was discovered in 2016 but the tests they thought of did not extend to tricking opcache into including some one else's file by creating a file with the same name.
I have also re-run the same test on our current dev VM, and the problem appears to have gone away, even if the configuration has changed so much that I'm not sure they're comparable anymore.
I am considering renaming shared-host config.inc.php files to something like config.a72b1qTy.inc.php.

Related

Set PHP include_path for site folders rather than entire web server

If this should be posted elsewhere please advise. I have reviewed other questions and answers but not been able to resolve my query so far.
I have wampserver running:
Server Software: Apache/2.4.23 (Win64) PHP/5.6.25 - Port defined for Apache: 80
My notebook o/s is Win10.
I am very new to this and just working through a PHP and MySQL book. I am putting multiple example sections in a local website for training so I don't have one area for includes but several with the root of the site structure:
eg: myweb/book01/includes; myweb/book02/includes
echo get_include_path(); shows .;C:\php\pear and I don't want to change the php.ini file as it may work for one myweb/book01 but not myweb/book02
I can set include paths manually for each page using ../ format but as the directory structure changes these fail unless manually edited which doesn't seem the right thing to do.
Is there a way to set an include_path that can be defined and used by specific pages and/or sections rather than a whole web root?
I can then just set once for each section (book01, book02) in the section's header file.
I have tried the following but keep getting 'Warning // failed to open stream' and ' Failed opening ... for inclusion' error messages:
set_include_path(get_include_path().":"."D\data\url\d4th\training\includes");
include $_SERVER['DOCUMENT_ROOT'] . '/training/includes/test_header.inc.php';
set_include_path ('.:/d4th/training/includes');
set_include_path('D:\data\url\d4th\training\includes');
set_include_path('.;D:\data\url\d4th\training\includes');
include ($_SERVER['HTTP_HOST'] . '/training/includes/header.inc.php'); // value for the above is 'd4th'
I'm sure there's a relatively straight forward solution. Been doing this for hours now and can't get my head round it.
You don't need to set the include path.
__DIR__
will always give you a prefix of your current directory.
<?php
include __DIR__.'/includes/test_header.inc.php';
There are much better ways to structure your applications. If you're just learning PHP, here's a great resource:
https://laracasts.com/series/php-for-beginners

Referring to the root directory

Before I start, this question may have been asked before, but I either don't know what to type to find it specifically and this is a slightly more specific case.
I'm developing a website with many dynamic ties, some of which is at the beginning of every php file there is the line require("global.php"); which does what the name states, among others such as the css file and whatever else is found on the root level. Problem is however, upon entering down into another directory, the link to this file is broken.
Traditionally what I've done (which is an absolutely stupid beginner workaround) is create a variable just before the require called $up which contained the string '../' which recurred respectively depending on how many directories deep the file is from the root. So the line would then appear require($up."global.php");.
I've realised how stupid this is and have tried to find ways around this. One of which is using the variable $_SERVER['DOCUMENT_ROOT'] to print out the root directory but the reason why this won't work 100% is because a) I'm developing the website on a local machine where the 'root' of the website in development is located in a subdirectory on the server and b) it returns the entire path based upon the location on the drive starting from D:\ and working its location that way, which I don't particularly like to work with when it comes to developing websites and would rather remain within the hosted directories of the web server if possible.
So, based on what I've explained, is there an easy way to get the root location of a website (regardless if the project is in a subridectory or the real root of the web server) within a short string that is extremely easy to append to the begining of every file reference in every php file regardless of it's directory level?
Thanks! <3
Suppose you have the following directory:
/srv/website/index.php
/srv/website/lib/functions.php
You could then do this:
define("MY_ROOT", $_SERVER['DOCUMENT_ROOT']);
And use it in every (included) file relatively:
require (MY_ROOT . "lib/functions.php" );

PHP path issues (localhost and server)

How do you guys handle the path of files to work either localhost and server without changing the variables?
For example, I have this:
$path = realpath($_SERVER["DOCUMENT_ROOT"]);
returns C:\xampp\htdocs
This would probably work on a online server, but doesn't work on a localhost, because I need to set the folder name of my project.
So, in my 100 files (for example) I would have to change to whenever I want to work in localhost:
$path = realpath($_SERVER["DOCUMENT_ROOT"]) . "/myproject/";
So I thought about a variable with a certain condition which would understand if it's localhost or server.
$path = (strpos(realpath($_SERVER["DOCUMENT_ROOT"]), "xampp") == false ? realpath($_SERVER["DOCUMENT_ROOT"]) : realpath($_SERVER["DOCUMENT_ROOT"]) . '/myproject');
And all I had to do:
<link href="<?php echo $path . '/css/bootstrap/bootstrap.css';?>" rel="stylesheet" type="text/css"/>
The above code does not work (at least in localhost, I haven't tried in a real server), because I get the following errors on console:
Not allowed to load local resource:
file:///C:/xampp/htdocs/myproject/css/bootstrap/bootstrap.css
Which I understand, so I have tried to change my $path variable to:
$path = (strpos(realpath($_SERVER["DOCUMENT_ROOT"]), "xampp") == false ? realpath($_SERVER["DOCUMENT_ROOT"]) : 'http://localhost/myproject');
And I get in the console:
Warning: require_once(): http:// wrapper is disabled in the server configuration by allow_url_include=0 in C:\xampp\htdocs\myproject\backend\orders.php on line 6
Is there any better way?
The most straightforward approach is to just create a local site with the same folder structure than your live site. I suspect you're doing it that way only because you aren't aware of Apache virtual hosts.
In any case, it's always practical to abstract paths as much as possible and constants are always a good choice. I typically define two:
WEB_ROOT to be used in URLs (in my case it's often just /)
FS_ROOT to be used in file system paths
You can feed them with hard-coded values in a settings file or dynamically calculating them. FS_ROOT is trivial to populate: you can use __DIR__ or dirname(__FILE__) for very old PHP versions.
Surprisingly, $_SERVER["DOCUMENT_ROOT"] does not provide accurate info in all hosting services.
Edit #1: I've appreciated a little base misconception in some of your comments so I'll try to shed some light on it.
Finding stuff on the Internet is not the same as finding stuff in your hard disc. The former makes use of URLs:
http://example.com/blog/latest-news?page=3
The latter makes use of file system paths:
/home/alice/pictures/kitten.jpg
C:\Users\Bob\Desktop\Shopping list.txt
In a web based PHP application you normally need to use both and you need to know the difference. You cannot use a URL to grab random files from a computer (not even yours) and you cannot use a file system path to grab anything from someone else's computer. This:
<link rel='stylesheet' href='C:\xampp\htdocs\myproject/css/bootstrap/bootstrap.css'/>
... is plain wrong because your linking a public resource with a file system path that will only work in your PC.
And one more thing... Most operating systems have adopted the convention to consider . and .. special directory names. That convention has been extended to URLs. But you must handle them as any other path component: it's backend/.. and not backend.. for the same reason that foo/bar is not the same as foobar.
Edit #2: Even if you don't use class auto-loading or the include_path directive, it's trivial to load your application wide settings file, either with absolute paths:
require_once(__DIR__ . '/../conf/configuration.php');
... or relative paths:
require_once('../conf/configuration.php');
IHMO, doing this once per script, in the top level PHP file, is a huge benefit over calculating all application paths every single time you use them.

Refer to website root in PHP (Windows)

I want to refer to my website root, or more exactly, to the directory above my script's one.
Let's say my website is example.com/test. I made a installation site which writes a config file. But it shouldn't write it to example.com/test/install/config.php, but to example.com/test/config.php. And the biggest pain in the ** is that I run on Windows (my development PC).
How do I do it?
If you want to get the web-site document root, you can use:
$_SERVER['DOCUMENT_ROOT']
That should work regardless of the operating system and gives you a path on the local file system (so no www.etcetc.).
If you want to get the fully-qualified path of your site root, from that file it is:
$root = realpath(dirname(__FILE__) . '/../../..');
The double-dots work their way up the directory structure, then realpath() is used to turn it into a proper path. So if you want to traverse one less folder up, use two sets of dots rather than three.

php include() from server root?

I'm deploying from my WAMP testing environment to an online test...
Locally I had my include paths something like this:
include('C/wamp/www...')
how do i find the equivalent path on my server?
i've tried using '/' to get to the root but i get this error:
Warning:
require_once(/test123/mvc/views/txt/index_nav_txt.php)
[function.require-once]: failed to
open stream: No such file or directory
in
/home/user/public_html/test123/mvc/views/components/st_footer.php
on line 37
Fatal error: require_once()
[function.require]: Failed opening
required
'/test123/mvc/views/txt/index_nav_txt.php'
(include_path='.:/usr/lib/php:/usr/local/lib/php')
in
/home/user/public_html/test123/mvc/views/components/st_footer.php
on line 37
You would actually need:
require_once("/home/codlife/public_html/test123/mvc/views/txt/index_nav_txt.php");
notice the edition of /home/codlife/public_html/
The initial / Takes you to the root of the server and your code is located inside /home/codlife/public_html/
do you mean
$_SERVER['DOCUMENT_ROOT']
which basically gives you the full path to your working website directory i.e. c:/wamp/www/(windows) or /var/www/vhost/domain.com/httpdocs/ (linux)
You should probably read up on include_path ( http://php.net/include_path ) - This is generally set to include the document root (where your website is) and can be altered so that you don't have to repeatedly include the same paths.
how do i find the equivalent path on my server?
You don't find it - you tell it where it should be. Admittedly this is not always practical when you buy a hosting package (which IME are usually badly supported and come with virtually no documentation).
First thing to note is regardless of where / how the code is hosted, you should always use paths relative to the directories configured on the php include path (or relative to the PHP script initially invoked by the browser request - the '.' entry from the include_path cited in the error) - never absolute paths. You can easily find this out with:
<?php
print ini_get('include_path');
?>
Judging from the path cited in the error message, it appears to be a POSIX system. The root of the filesystem as seen by the webserver might be quite different from the root as seen from your FTP or SSH software, but they are probably the same.
Note that if this is a shared host, then you probably won't have access to put files in /usr/lib/php or /usr/local/lib/php - so your only option is to use a relative path - which is going to get very messy -
You could do some clever coding around this - but do have a look at packages such as Dokuwiki and phpmyadmin to see how they organise the include files in a relocateable way without any dependance on manipulating the php.ini settings.
Alternatively you may be able to override the include_path via .htaccess, e.g.
php_value include_path ".:/home/codlife/public_html:/usr/lib/php:/usr/local/lib/php"
(which would set a base include_path to your document root)
HTH
C.
Use a configuration file where you store things like:
$application_root = '/home/code_life/public_html/';
In this file use all your environment specific variables or constants. When you deploy the application on a different machine, you just update configuration file.
Example:
You have in your root application a folder called settings with settings.php where you can define:
define('DIR_ROOT', dirname(dirname(__FILE__)) . '/');
Now, on every machine, the DIR_ROOT will be the root of your application and you don't have to change anything.

Categories