When hosting multiple websites on a single Apache server, proper isolation between VirtualHosts is critical. The core issues we face are:
- Prevent cross-site file access (read/write/execute)
- Restrict access to system configuration files
- Maintain proper PHP execution boundaries
- Allow necessary server functionality
While SUEXEC prevents users from modifying others' files, it doesn't solve the information leakage problem. Consider this common vulnerability:
<?php
// Attacker could read sensitive files
echo file_get_contents('/etc/passwd');
?>
The most effective solution combines chroot with proper permission hardening. Here's how to set it up on Ubuntu:
# Create the chroot structure
sudo mkdir -p /var/www/chroot/{dev,etc,lib,usr,var}
sudo cp /etc/ld.so.cache /var/www/chroot/etc/
sudo cp /etc/ld.so.conf /var/www/chroot/etc/
sudo cp /etc/nsswitch.conf /var/www/chroot/etc/
sudo cp /etc/hosts /var/www/chroot/etc/
# Copy required libraries
for lib in $(ldd /usr/lib/cgi-bin/php5-cgi | grep "=>" | awk '{print $3}'); do
sudo cp --parents $lib /var/www/chroot/
done
Configure each VirtualHost to run in its own chroot environment:
<VirtualHost *:80>
ServerName site1.example.com
DocumentRoot /var/www/chroot/site1/public_html
<Directory /var/www/chroot/site1/public_html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
SuexecUserGroup site1 site1
ScriptAlias /cgi-bin/ /var/www/chroot/site1/cgi-bin/
<Directory "/var/www/chroot/site1/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Require all granted
</Directory>
<FilesMatch \.php$>
SetHandler php5-fcgi
Action php5-fcgi /php5-fcgi virtual
</FilesMatch>
</VirtualHost>
Implement these permission settings for maximum security:
# Set proper ownership
sudo chown -R site1:site1 /var/www/chroot/site1
sudo chown root:root /var/www/chroot/site1
# Remove global permissions
sudo chmod -R o-rwx /var/www/chroot/site1
sudo find /var/www/chroot/site1 -type d -exec chmod 750 {} \;
sudo find /var/www/chroot/site1 -type f -exec chmod 640 {} \;
# Special permissions for upload directories
sudo chmod 770 /var/www/chroot/site1/public_html/uploads
When implementing this solution, you might encounter:
# PHP session handling in chroot
sudo mkdir -p /var/www/chroot/site1/tmp
sudo chmod 1733 /var/www/chroot/site1/tmp # Sticky bit for tmp
For database connections, ensure your PHP applications use TCP/IP connections instead of Unix sockets, as these won't be available in the chroot.
Implement these checks in your maintenance routine:
# Check for permission violations
sudo find /var/www/chroot -type f -perm -o+r -ls
sudo find /var/www/chroot -type d -perm -o+rx -ls
# Verify chroot integrity
sudo chroot /var/www/chroot/site1 /usr/bin/php -v
When hosting multiple client websites on a single Apache instance, we face three critical security requirements:
- Prevent cross-site file access
- Restrict system resource visibility
- Maintain individual script execution sandboxes
The traditional mod_suexec
approach only solves part of the problem. While it enforces proper ownership for file writes and executes, it doesn't prevent:
# Example of problematic visibility
$config = file_get_contents('/var/www/client2/database.php');
# Or even worse:
$shadow = file('/etc/shadow');
The most effective solution combines three layers:
1. Filesystem Isolation
/var/www/
├── jail1/ # chroot for client1
│ ├── www/ # DocumentRoot
│ └── tmp/ # Isolated temp space
├── jail2/ # chroot for client2
│ ├── www/
│ └── tmp/
└── .../
2. Permission Hardening
Set strict directory permissions:
# Recursively remove 'other' permissions
find /var/www/jail* -type d -exec chmod 750 {} \;
find /var/www/jail* -type f -exec chmod 640 {} \;
# Set proper ownership
chown -R client1:client1 /var/www/jail1
chown -R client2:client2 /var/www/jail2
3. Apache Configuration
Combine with mod_fcgid
:
<VirtualHost *:80>
ServerName client1.example.com
DocumentRoot /var/www/jail1/www
SuexecUserGroup client1 client1
FcgidWrapper /var/www/jail1/php-fcgi-wrapper .php
<Directory /var/www/jail1/www>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
For shared resources that must be accessible:
# Create bind mounts in each jail
mount --bind /usr/share/zoneinfo /var/www/jail1/usr/share/zoneinfo
mount --bind /dev/urandom /var/www/jail1/dev/urandom
Test isolation with:
<?php
# Attempt to break out
var_dump(@file_get_contents('../../jail2/www/config.php'));
var_dump(@scandir('/etc'));
?>
Properly configured, both should return false/empty.
For environments where chroot isn't feasible:
- Docker containers per VirtualHost
- OpenVZ/Virtuozzo containers
- SELinux context separation