8.1. Exposed Source Code
Your web server must be able to read your source code in order to execute it, and this means that anyone else who can write code that your web server executes can also read your source code. On a shared host, this is a significant risk because the web server is shared, and a simple PHP script written by another developer on your server can read arbitrary files:
<?php header('Content-Type: text/plain'); readfile($_GET['file']); ?>
With this script running on the same server as your source code, an attacker can view any file that the web server can read by indicating the full path and filename as the value of file. For example, assuming this script is named file.php and hosted at example.org, a file such as /path/to/source.php can be exposed simply by visiting:
Of course, the attacker must know the location of your source code for this simple script to be useful, but more sophisticated scripts can be written to allow an attacker to conveniently browse the filesystem. An example of such a script is given later in this chapter.
There is no perfect solution to this problem. As described in Chapter 5, you should consider all source code stored within document root to be public. On a shared host, you should consider all of your source code to be public, even the code that you store outside of document root.
A best practice is to store all sensitive data in a database. This adds a layer of complexity to some scripts, but it is the safest approach for protecting your sensitive data from exposure. Unfortunately, one problem still remains. Where can you safely store your database access credentials?
Consider a file named db.inc that is stored outside of document root:
<?php $db_user = 'myuser'; $db_pass = 'mypass'; $db_host = 'localhost'; $db = mysql_connect($db_host, $db_user, $db_pass); ?>
If the path to this file is known (or guessed), another user on your server can potentially access it, obtain your database access credentials, and gain access to your database, including all of the sensitive data that you are storing there.
The best solution to this particular problem is to keep your database access credentials in a file that only root can read and that adheres to the following format:
SetEnv DB_USER "myuser" SetEnv DB_PASS "mypass"
SetEnv is an Apache directive, and the format of this file instructs Apache to create environment variables for your database username and password. Of course, the key to this technique is that only the root user can read the file. If you do not have access to the root user, you can restrict read privileges to yourself only, and this offers similar protection:
$ chmod 600 db.conf $ ls db.conf -rw------- 1 chris chris 48 May 21 12:34 db.conf
This effectively prevents a malicious script from accessing your database access credentials, so you can store sensitive data in the database without a significant risk of it being compromised.
For this file to be useful to you, you need to be able to access this data from PHP. To do this, httpd.conf needs to include this file as follows:
Because Apache's parent process runs as root (this is required for a process to bind to port 80), it can read this configuration file, but child processes that serve requests (and execute PHP scripts) cannot.
You can access these variables in the $_SERVER superglobal array, so db.inc can reference $_SERVER variables instead of revealing the database access credentials:
<?php $db_user = $_SERVER['DB_USER']; $db_pass = $_SERVER['DB_PASS']; $db_host = 'localhost'; $db = mysql_connect($db_host, $db_user, $db_pass); ?>
If this file is exposed, the database access credentials are not revealed. This offers a significant increase in security on a shared host, and it is also a valuable Defense in Depth technique on a dedicated host.
Of course, you can use this technique to protect any information (not just your database access credentials), but I find it more convenient to keep most data in the database, especially because this technique requires some cooperation from your hosting service provider.