If you are an agency building websites for your customers, and managing those sites on a Virtual Private Server (VPS), this post is for you.
While working on a recent forensics case, we encountered one of the most egregious mistakes we see with organizations managing multiple websites on one server.
All websites were owned by the default web server user and group.
Cross Site Contamination
There are many aspects to securing your VPS, this article will focus on isolating each site into their own container within the same VPS. It’s an extension of the functional isolation principle, and serves to tackle one very specific threat cross-site contamination.
Cross-site contamination is a phenomena where a site[s] is compromised by it’s neighboring sites. This is very common in Agencies, where they manage and maintain their own servers and you find what we call “soup kitchen servers”.
This type of threat has the ability to bypass any defensive controls you deploy for a specific website because it’s a function of lateral movement inside the existing server. Bad attacker exploits a vulnerability, gains access to the environment and the associated users, and using those credentials is able to traverse other websites on the same server.
This threat is often a function of poor server configurations and management. The most prevalent misconfiguration is when all the sites on the server are owned by the same user and group.
In 2012, Daniel wrote an article WordPress and Server Hardening – Taking Security to Another Level in which he provided some techniques to leverage separate users and remove WordPress’ ability from writing to itself. This series will work to expand on those ideas, and modernize the approach for what we might expect from users in 2020 and beyond.
Create Dedicated Users and Groups for Each Site
The key to reducing the risk of cross-site contamination is to ensure every site on the server is owned and managed by it’s own user / group. This is exactly what most shared hosts do.
We are going to assume a traditional LAMP stack for this guide, but the same can be applied for the various LAMP-like configurations available.
Here are the specs of the server we’ll demonstrate on:
Category | Detail |
---|---|
Operating System | Ubuntu 18.04.4 LTS |
PHP | PHP 7.2.24 |
Apache | Apache/2.4.29 |
What you will need:
Dependency | Description |
---|---|
PHP-FPM | FastCGI Process Manager (FPM) |
In the old days, server administrators would use suPHP to do what we’re about to do. PHP-FPM, however, does away with the need for suPHP and introduces this idea of Pools.
These pools are containers for a lack of a better word that have the ability to isolate PHP requests, isolating them to specific users.
For instance, by default Apache on Ubuntu will create a default user of www-data and group of www-data. This is traditionally the default user and group combination most web server admins use for their sites.
With PHP-FPM we’ll be able to leverage site specific users and groups for each site.
Domain | User / Group |
---|---|
domain1.com | domain1 / domain1 |
domain2.net | domain2 / domain2 |
Step 1: Install PHP-FPM
Installing PHP-FPM is extremely straight forward. Open your terminal and run:
# apt-get install php-fpm
Once installed, leave it alone. We’ll come back to it later, once we’ve configured the rest.
Step 2: Create New User / Group
Go ahead and create the user you want for your domain.
# adduser domain1
Follow the prompts to create a complex, long and unique password. By default, this user will be added to your /home directory.
Step 3: Assign Ownership to Files
Assign the files for your domain to this new user / group.
# chown -R domain1:domain1 /var/www/domain1.com/
Step 4: Verify Permissions and Files are Set correctly
Make sure the directories are set to the right permission:
find /var/www/domain1.com/ -type d -exec chmod 755 {} +
Make sure the files are set to the right permission:
find /var/www/domain1.com/ -type f -exec chmod 644 {} +
Step 5: Configure PHP-FPM
Now, we’ll configure PHP-FPM. First thing to note is the location of the configuration files.
All pool files must have a .conf extension, and will reside under this directory by default:
/etc/php/7.2/fpm/pool.d/
By default, PHP-FPM will come with a default configuration file titled: www.conf
Copy this configuration file, and create a new configuration file for each site.
# cp /etc/php/7.2/fpm/pool.d/www.conf /etc/php/7.2/fpm/pool.d/domain1.conf
This configuration file is creating a new container. I titled the configuration file the name of my domain to make it easier to remember what I did.
Open the new domain1.conf file and update the following sections:
; Start a new pool named 'www'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('www' here)
[www]
You are going to update [www] with a new name, [domain1]. This will be the new pool name.
; Start a new pool named 'www'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('www' here)
[domain1]
Update the user / group allowed to use the processes in this new container (pool):
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
; will be used.
user = www-data
group = www-data
In this instance, www-data will be replaced with domain1 (created in Step 2).
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
; will be used.
user = domain1
group = domain1
Lastly, you want to tell PHP-FPM where to listen for requests. You do that in section:
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
; a specific port;
; 'port' - to listen on a TCP socket to all addresses
; (IPv6 and IPv4-mapped) on a specific port;
; '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = /run/php/php7.2-fpm.sock
And you do it, by appending a listener to local host and on a specific port. It looks like this:
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
; a specific port;
; 'port' - to listen on a TCP socket to all addresses
; (IPv6 and IPv4-mapped) on a specific port;
; '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = /run/php/php7.2-fpm.sock
listen = 127.0.0.1:9000
Tech Note: Every new configuration file should have a unique port number or you will run into issues where the user you created can’t execute PHP.
Step 6: Update VHosts Files
Lastly, you need to update your VHOSTS for each domain to route PHP requests to your localhost, and the specific port you specified. This is what tells the container when, and for who, to process the PHP requests.
Navigate to where you have your vhosts file:
# vim /etc/apache2/sites-enabled/domain1.com-le-ssl.conf
Add this between your <VirtualHosts> directives:
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://127.0.0.1:9004/"
</FilesMatch>
This is looking for all PHP requests and routing it to localhost (127.0.0.1) and port 9004. Your Vhost file could possibly look like this:
<VirtualHost *:443>
ServerAdmin webmaster@domain1.com
ServerName domain1.com
DocumentRoot /var/www/domain1.com
ErrorLog ${APACHE_LOG_DIR}/domain1.com.error.log
CustomLog ${APACHE_LOG_DIR}/domain1.com.access.log combined
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://127.0.0.1:9004/"
</FilesMatch>
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/domain1.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/domain1.com/privkey.pem
</VirtualHost>
Step 7: Restart Services
Now, restart the services. You have to restart Apache and PHP-FPM separately.
Apache:
# systemctl restart apache2.service
PHP-FPM
# systemctl restart php7.2-fpm.service
You can test your configuration by doing a quick test. This will create a file called info.php and add the phpinfo function to the file.
# echo ‘<?php phpinfo(); ?>’ > /var/www/domain1.com/info.php
Open the file via a browser (e.g., domain1.com/info.php) and this is what you are looking for:
This shows me that PHP-FPM is active and working. You should be able to update and make changes via your CMS as needed. The heavy lifting is happening on the backend for you.
Reducing Risk Exposure
Managing a portfolio of sites on your servers does not need to be daunting task. It usually is, but not because it’s difficult as much as the organization just doesn’t know where to start.
In fact, it can be extremely efficient and cost-effective for an organization. It gives you full control of the machine, it’s content, and visibility into what is happening. This will prove invaluable if there is ever an incident.
The problem often boils down to a lack of experience and fear of the potential security concerns. That is why we’ll be sharing a series of articles designed to help Agencies, and other service providers, get a better handle on how they can leverage Dedicated / VPS servers more effectively as part of their service offering.
The first thing to note, and what you’ll hear us harp on continuously, is that security requires a layer approach. It’s not about tackling every problem day one, but about identifying the problems that are most critical and systematically mitigating them.
The example above is a perfect illustration of risk reduction. A web server housed multiple websites and was at high risk of cross-site contamination. A few minor changes reduced the risk of cross-site contamination by isolating each site on the server into its own PHP container with site specific users and groups. Now, the site owner can make whatever changes they want, update their CMS accordingly, and if one site is ever compromised they can rest easy that their entire portfolio isn’t affected.
If you need help securing your organizations web servers, please contact us at info@coldpath.net to learn more about our consultative services.
Leave a Reply