On a shared server with multiple virtual hosts, we need to think about how to keep the files and scripts separate from one another. By default, PHP files are executed as “apache” or “nobody” – these are the default users that Apache creates. This creates security issues since PHP files need wide permissions and it leaves you vulnerable to code injection, and other alterations of PHP files from other users on the same server.
When we first install PHP on Linux, by default we see the following when we run “phpinfo()”:
Note that the “Server API” has the value “Apache 2.0 Handler”. What this means is that PHP is not running as a separate process for each script. Rather, it runs as part of Apache itself. This is usually an extremely efficient way of doing this. But as we see below, it might not be the most secure.
As an example, take the following PHP script:
<?php echo 'PHP running as: ' . exec('whoami'); echo '<br>Current script owner: ' . get_current_user(); ?>
When I place this file in my document root and run it, I get the following output:
As you can see, PHP is running as “apache” (on other systems, it might be “nobody”). Ideally, we would want every script to be executed by its owner. In this case, root. What we want is a PHP process where the two lines above show the same username.
Installing suPHP Instead of the Apache Handler
To correct this, we use a module known as “suPHP”. Despite development on suPHP stopping a while ago, where it only supported lower versions of Apache, it still remains popular. Luckily, the community continues to issue some patches, and a user named Aki Tuomi helped with issuing a patch for suPHP that allows it to work with Apache. Here’s how to install it on CentOS 7.
Since suPHP is no longer in active development, it’s not directly available in official repositories. As a result, we need to compile it from scratch. To do this, we have to install some development tools using the following three commands:
yum -y groupinstall 'Development Tools' yum -y install apr-devel yum -y install httpd-devel
Without the last two commands, you’ll get an error during compilation that says:
checking for APR... configure: error: the --with-apr parameter is incorrect. It must specify an install prefix, a build directory, or an apr-config file.
Once the development tools are installed, it’s time to download the source code, patch it, and compile it.
Download, Patch, and Compile:
First, we download and extract the latest suPHP into a temp folder:
mkdir temp cd temp wget http://suphp.org/download/suphp-0.7.2.tar.gz tar zxvf suphp-0.7.2.tar.gz
Now we download and install the patch:
wget -O patchingsuphp.patch https://www.webhostinghero.com/downloads/php/suphp.patch patch -Np1 -d suphp-0.7.2 < patchingsuphp.patch cd suphp-0.7.2 autoreconf -if
Now we run the “configure” script in the folder like this:
./configure --prefix=/usr/ --sysconfdir=/etc/ --with-apr=/usr/bin/apr-1-config --with-apache-user=apache --with-setid-mode=owner --with-logfile=/var/log/httpd/suphp_log
And when that completes, we compile:
make make install
This compiles and installs suPHP on CentOS 7. But we’re not done by a long shot. First, we configure it to work with our installation:
Apache has a special folder called /etc/httpd/conf.d/ which contains a whole bunch of modules in the form of configuration files. We need to create one for suPHP as well.
Create the following file using an editor like vi:
And put the following line into it:
LoadModule suphp_module modules/mod_suphp.so
The above line tells Apache to include suPHP as a module. And now we need to configure suPHP itself. Create a file using a file editor like this:
And paste the following inside:
[global] ;Path to logfile logfile=/var/log/httpd/suphp.log ;Loglevel loglevel=info ;User Apache is running as webserver_user=apache ;Path all scripts have to be in docroot=/ ;Path to chroot() to before executing script ;chroot=/mychroot ; Security options allow_file_group_writeable=true allow_file_others_writeable=false allow_directory_group_writeable=true allow_directory_others_writeable=false ;Check wheter script is within DOCUMENT_ROOT check_vhost_docroot=true ;Send minor error messages to browser errors_to_browser=false ;PATH environment variable env_path=/bin:/usr/bin ;Umask to set, specify in octal notation umask=0077 ; Minimum UID min_uid=100 ; Minimum GID min_gid=100 [handlers] ;Handler for php-scripts x-httpd-suphp="php:/usr/bin/php-cgi" ;Handler for CGI-scripts x-suphp-cgi="execute:!self"
This takes care of suPHP configuration. But we’re not done yet! For suPHP to work, we need to create virtual hosts. Merely restarting Apache now won’t magically change the Apache handler to suPHP. For that to work, we have to set up our domains inside the document root directory.
Creating Virtual Hosts
This step is necessary for all the above to work. First, find out what your current document root is by executing the following command:
grep -i 'DocumentRoot' /etc/httpd/conf/httpd.conf
In my example, the document roots is /var/www/html . So I’ll be using that in my examples. Make sure you change it whatever shows up for you.
Create folder and Owner:
Next, we need to create a separate folder for our domain name (or names) in the document root. In my case, I’m just using an IP address, so I create a folder. My directory structure is now like this:
Replace the part in bold with your own domain name or IP address. Next, we need to create an owner of the domain folder. Either create a new user, or you can assign an existing owner like this:
chown -Rf [user]:[user] /var/www/html/[domainname]
Note that [domainname] includes the “.com” or “.net” TLD. As before, replace [user] with an existing or new username and [domainname] with the name of the directory you just created. After this, whenever PHP creates files in [domainname] they will belong to [user].
Creating the Virtual Hosts file:
Just like adding a new module to Apache in /etc/httpd/conf.d/, we create a separate virtual hosts configuration file for each directory associated with a domain name. The name of the file, is the [folder/domainname].conf . Using a text editor, create a new file like this:
And paste the following into it:
<VirtualHost *> DocumentRoot /var/www/html/[domainname] ServerName [domainname] ServerAdmin webmaster@[domainname] <FilesMatch ".+\.ph(p?|t|tml)$"> SetHandler None </FilesMatch> <IfModule mod_suphp.c> suPHP_Engine on <FilesMatch "\.php?$"> SetHandler x-httpd-suphp </FilesMatch> suPHP_AddHandler x-httpd-suphp </IfModule> </VirtualHost>
This is the final piece of the configuration puzzle. Now just restart Apache using:
systemctl restart httpd.service
And we’re done!
To test this, create a file in the new [domainname] directory under the document root and make sure that it’s owned by the group [user] and the owner is also [user]. To directly manipulate files on a remote server using a nice GUI interface, I recommend WinSCP. You can read my earlier tutorial on how to setup WinSCP securely on Linux. In the screenshot below, you can see my new file has both the group owner and user owner as “bhagwad:
Note how the permissions of the file are 0644. This is exactly in line with what is required by suPHP. In this new file, run phpinfo using the following code:
<?php phpinfo(); ?>
This will now give you the output as shown here:
Note how the “Server API” value has changed from “Apache 2.0 Handler” to “CGI/FastCGI”. This is the test you will use to know if your suPHP installation is working. If it remains “Apache 2.0 Handler”, it means something has gone wrong.
And also when I place the very first PHP file we ran and assign it the proper permissions, this is the output:
As you can see, PHP is now running as “bhagwad”, which is the same as the script owner. In fact, if you now try and run a script with a different owner, you’ll get a 500 permissions error. Your PHP process in a shared hosting environment is now secure!