Web Server in a Sandbox

Submitted by h2b on
Problembeschreibung
System
Debian GNU/Linux
Version
7
Symptom
A web server and related services as a database server shall run in jailed environments, so that they have no or or only limited access to other processes or filesystem paths of the operating system.

There are different methods to get a sandbox environment. Here, we use the  Firejail Security Sandbox, which allows to assign a private sealed scope to a service and all associated processes; this includes resources like network access, process table or filesystem. Therewith, the service only sees its own processes and can only access the part of the filesystem that has been assigned tio it.

A very helpful source for this howto was the articlel How To Use Firejail to Set Up a WordPress Installation in a Jailed Environment, which describes a WordPress installation including Firejail, Nginx and MySQL. In contrast to that article we restrict to the installation of the basic web and database services, especially Apache and PostgreSQL; on this basis, the reader may set up content-management systems (like Drupal or after all WordPress) or completely different services as usual. A further difference to the mentioned article is, that all scripts work with a specified target IP address, i.e., it is possible to run other services on the same machine, independent from the services described here, as long as they respond to other IP addresses.

We will set up the services so that they communicate with each other and the outer world over a bridge. The IP addresses involved are shown in the following table:

Service Public IP Address Bridge IP Address
Host xxx.xxx.xxx.xxx 10.10.20.1
Web server none 10.10.20.10
Database server none 10.10.20.20

Contents

1 Preliminary

2 Constituting the Traffic

3 Creating the File Systems

4 Setting Up the Services

4.1 Web Server

4.2 Database

5 Starting the Services

6 Conclusion

1. Preliminary

This howto applies to Debian systems.

 

In case of – as happens to be for Debian 7 ("Wheezy") – Firejail is not available as a system package, it has to be downloaded from Firejail/Download and be installed.

 

Furthermore, we need a package to set up a minimal filesystem

 

apt-get install debootstrap

 

and tools to set up the bridge:

 

apt-get install bridge-utils

In /etc/network/interfaces the public IP address has to be configured, e.g.,

iface eth0 inet static
    address yyy.yyy.yyy.yyy
    netmask 255.255.255.0
    gateway yyy.yyy.yyy.1
   
    up ip addr add xxx.xxx.xxx.xxx/32 dev eth0 label eth0:0

where yyy.yyy.yyy.yyy is the "main" IP addresse of our machine and xxx.xxx.xxx.xxx is the public address of the host to be configured by this howto.

If still an Apache is running on this system that shall listen to IP addresses different from the one set up here, it has to be configured to listen to these addresses exactly. To achieve this, in its configuration files any  _default_ entries  have to be changed to the actual IP address, respectively. As a template, you can use the entries for ports.conf and sites-available/* of the table in the Webserver section.

2. Constituting the Traffic

We start with some definitions.

Of course, the public target address of our services has to be accommodated individually:

DESTINATION_IP="xxx.xxx.xxx.xxx"

Further definitions – as a general rule –  can be taken over as they are:

INTERFACE="eth0+"
TCP_SERVICES="80 443" #web browsing
BRIDGE_HOST_IP="10.10.20.1"
BRIDGE_HOST_IP_RANGE=$BRIDGE_HOST_IP"/24"
BRIDGE_WEBSERVER_IP="10.10.20.10"
BRIDGE_DBSERVER_IP="10.10.20.20"

We create and configure our bridge:

brctl addbr br0
ifconfig br0 $BRIDGE_HOST_IP_RANGE

The operating systems allows forwarding of IP addresses by:

echo "1" > /proc/sys/net/ipv4/ip_forward

We allow traffic that is dedicated to our web server to be forwarded through our bridge:

for PORT in $TCP_SERVICES; do
  iptables -t nat -A PREROUTING -d $DESTINATION_IP -p tcp --dport $PORT -j DNAT --to $BRIDGE_WEBSERVER_IP:$PORT
done

Vice versa, traffic that is going to the outside world shall be masqueraded by the public address:

iptables -t nat -A POSTROUTING -o $INTERFACE -j MASQUERADE

Traffic coming from the dedicated ports is allowed:

for PORT in $TCP_SERVICES; do
  iptables -A FORWARD -i $INTERFACE -o br0 -p tcp -m tcp --dport $PORT -j ACCEPT
done

Also, incoming traffic of already established connections is allowed:

iptables -A FORWARD -i $INTERFACE -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT

We allow all traffic that comes from our bridge to put the jails in a position to communicate with each other and the outside world:

iptables -A FORWARD -i br0 -j ACCEPT

All other forwarding will be dropped:

iptables -P FORWARD DROP

Optionally, we may allow traffic originating from our host to access the database server:

iptables -I OUTPUT -p tcp -s $BRIDGE_HOST_IP -d $BRIDGE_DBSERVER_IP -j ACCEPT

This is convenient if we want, let's say, to administrate the database with a service like phpPgAdmin over a separate IP address.

A template for an /etc/init.d script is attached below. Before running it, at least DESTINATION_IP has to be set to the public IP address of the host.

3. Creating the File Systems

We create a directory for the filesystems that later will be caught in a andbox and change to this:

mkdir /jail
cd /jail

Then, we create a minimal filesystem for the web server

debootstrap --arch=amd64 stable www

and make a copy for the database server

rsync -a www/ db

The latter is less expensive than a new debootstrap which would be an alternative .

By the way, the sandbox operating system does not necessarily need to be equal to to the host operating system. At the time of writing this document, my Debian-7 host system already was "oldstable"; regardless, it worked with "debootstrap ... stable ..." and it was possible to install and operate software from the stable branch within the jails.

4. Setting Up the Services

4.1  Web Server

To set up the web server, we start firejail within the filesystem created before

firejail --chroot=/jail/www/ --name=www

and install the web-server software (in this case Apache):

apt-get update
apt-get install apache2

Next we have to configure Apache for the environment in which it shall run. We can do this within the enviroment started by firejail (then we are working within the directory /etc/apache2) or from outside (then the configuration directory is /jail/www/etc/apache2); in both cases we are working on the same directory, only the root of the filesystem is different.

The following table shows the important entries to be made:

File Change
ports.conf Set Listen directive to 10.10.20.10:80 resp. 10.10.20.10:443
conf-available/servername.conf (create file if necessary) One entry only: ServerName 10.10.20.10
sites-available/000-default.conf Set VirtualHost and ServerName to 10.10.20.10:80
sites-available/default-ssl.conf Set VirtualHost and ServerName to 10.10.20.10:443
envvars (optional) export APACHE_LOG_DIR=/var/local/log/apache2$SUFFIX (see below)

On starting of the jail the directory /var/log will be moved to a temporary location. To get the Apache log files permanently, we can change the log directory in the configuration file envvars as described above (here to /var/local/log/apache2), but have to create the corresponding directory structure manually: mkdir -p /var/local/log/apache2 (within the jail).

To enable the server name. we call the command (within the jail)

a2enconf servername.conf

Besides, all "normal" configurations have to be done that are required or desired for the opration of the web site.

If the web server is already running, we should stop it by

service apache2 stop

before leaving the firejail environment by

exit

4.2 Database

Regarding to the database server, we proceed accordingly, i.e., first

firejail --chroot=/jail/db/ --name=db

(now specifying db as chroot and name) and then (for PostgreSQL)

apt-get update
apt-get install postgresql

In the file /etc/postgresql/9.4/main/postgresql.conf (within the jail, adjust the version number accordingly) the following configurations have to be made:

Entry Value Remark
listen_addresses '10.10.20.20' Listen to our bridge IP.
logging_collector on Optional for logging.
log_directory '/var/local/log/postgresql' Optional, to be consistent with www logging.

When enabling the alternative log_directory, we have to create it and set appropriate permissions (within the jail):

mkdir -p /var/local/log/postgresql
chmod -R g-rwx,o-rwx /var/local/log/postgresql
chown -R postgres:postgres /var/local/log/postgresql

Of course, all other "normal" operation configurations have to be done here too.

If the database server is already running, we should stop it by

service postgresql stop

before leaving the firejail environment by

exit

Note Despite intensive attempts using diverse configurations and doing exhaustive investigations over several days I failed to set up a MySQL server with Firejail. Although the server started, it always immediately was shutting down without leaving useful error or log messages. For this reason, I finally chose PostgreSQL.

5. Starting the Services

For set-up purposes we could start the jails as described above. To provide a network connection and to start the servers automatically in the background these commands are needed:

firejail --name=www --chroot=/jail/www --private --net=br0 --ip=10.10.20.10 sh -c "service apache2 start; sleep inf" &
firejail --name=db --chroot=/jail/db --private --net=br0 --ip=10.10.20.20 sh -c "service postgresql start; sleep inf" &

The option --private ensures that certain directories of the original filesystem like /root or /home are not visible, --net and --ip establishes the routing and the succeeding command is executed in the jail using those parameters: web and database server are started and sleep inf ensures that the sandbox is still running even when the Apache or PostgreSQL process ends or crashes (the latter might be of interest for administrative ends). Note: sh -c is emphasized because in contrast to other descriptions it didn't work for me without this.

The running firejail processes can be listed by:

firejail --list

To join a running sandbox we call

firejail --join=www

or

firejail --join=db

Stopping of the servers can be done by

firejail --shutdown=www
firejail --shutdown=db

Scripts for init.d are attatched below.

6. Conclusion

Using the described procedure one gets a web server and a database server, both running in a sealed jail, on the same machine within an internal IP range.

Within the web-server jail one can install a CMS like Drupal or WordPress as usual now, merely paying attention to the fact that the database has to be accessed by an internal IP address (10.10.20.20).

By the sealing it is achieved that, e.g., an attacker that could intrude by SQL injection only can cause damage within the database jail, but not in the rest of the system. Although such security measures never can be perfect, at least we have made the attacker a very hard work.

Attachment Size
jailbridge.template.gz 1.24 KB
jail-www.gz 472 bytes
jail-db.gz 471 bytes
Bereich