You are here

Webserver im Sandkasten

Submitted by h2b on 23. June 2017 - 18:19
Problembeschreibung
System: 
Debian GNU/Linux
Version: 
7
Symptom: 
Ein Webserver und zugehörige Dienste wie ein Datenbankserver sollen in jeweils abgeschlossenen Umgebungen (jail bzw. sandbox) laufen, so dass diese auf andere Prozesse oder Dateisystembereiche des Betriebssystems nicht oder nur eingeschränkt zugreifen können.

Es gibt verschiedene Techniken, um Sandbox-Umgebungen herzustellen. Hier verwenden wir die Firejail Security Sandbox, die es ermöglicht, einem Dienst und allen zugehörigen Prozessen Betriebsmittel wie Netzwerkzugriff, Prozesstabelle oder Dateisystem in einem privaten, abgeschotteten Bereich zuzuteilen. Der Dienst sieht damit nur seine eigenen Prozesse und kann nur auf den Teil des Dateisystems zugreifen, der ihm zugeordnet wird.

Eine sehr hilfreiche Quelle für diese Anleitung war der Artikel How To Use Firejail to Set Up a WordPress Installation in a Jailed Environment, der eine WordPress-Installation mit Firejail, Nginx und MySQL beschreibt. Im Unterschied dazu beschränken wir uns hier auf die Installation der Grunddienste Web- und Datenbankserver, und zwar speziell Apache und PostgreSQL; darauf kann der geneigte Leser dann beliebige Content-Management-Systeme (wie Drupal oder eben auch WordPress) oder ganz andere Dienste wie gewohnt aufsetzen. Ein weiterer Unterschied ist, dass alle Skripte eine gegebene Ziel-IP-Adresse verarbeiten, d. h., auf der Maschine können unabhängig von den hier beschriebenen noch andere Dienste laufen, die auf sonstige IP-Adressen reagieren.

Wir werden die Dienste so einrichten, dass sie über eine Bridge untereinander und mit der Außenwelt kommunizieren. Die dabei beteiligten IP-Adressen stellt folgende Tabelle dar:

Dienst Öffentliche IP-Adresse Bridge-IP-Adresse
Host xxx.xxx.xxx.xxx 10.10.20.1
Webserver keine 10.10.20.10
Datenbankserver keine 10.10.20.20

Inhalt

1 Vorbereitung

2 Einrichten des Datenverkehrs

3 Erstellen der Dateisysteme

4 Einrichten der Dienste

4.1 Webserver

4.2 Datenbank

5 Starten der Dienste

6 Schlussfolgerung

1. Vorbereitung

Diese Anleitung bezieht sich speziell auf Debian-Systeme.

 

Falls – wie bei Debian 7 ("Wheezy") – Firejail nicht als Systempaket verfügbar ist, muss es von Firejail/Download heruntergeladen und installiert werden.

 

Desweiteren benötigen wir ein Paket zum Einrichten eines minimalen Dateisystems

 

apt-get install debootstrap

 

und Hilfsprogramme für die Bridge-Einrichtung

 

apt-get install bridge-utils

In /etc/network/interfaces muss die öffentliche IP-Adresse unseres Hosts eingerichtet sein, z. B.

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

wenn yyy.yyy.yyy.yyy die "Haupt"-IP-Adresse unserer Maschine ist und xxx.xxx.xxx.xxx die öffentliche Adresse des mit dieser Anleitung zu konfigurierenden Hosts.

Falls auf dem System noch ein Apache läuft, der auf andere IP-Adressen hören soll als der hier von uns aufgesetzte, muss dieser so eingerichtet sein, dass er genau auf jene Adressen reagiert. Dazu müssen etwaige _default_-Einträge in den Konfigurationsdateien in die tatsächliche IP-Adresse geändert werden. Als Vorlage kann man die Einträge zu ports.conf und sites-available/* in der Tabelle im Abschnitt Webserver verwenden.

2. Einrichten des Datenverkehrs

Wir beginnen mit einigen Definitionen.

Die öffentliche Zieladresse unseres Services muss natürlich individuell angepasst werden:

DESTINATION_IP="xxx.xxx.xxx.xxx"

Die weiteren Definitionen kann man in der Regel unverändert übernehmen:

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"

Wir erzeugen und konfigurieren unsere Bridge:

brctl addbr br0
ifconfig br0 $BRIDGE_HOST_IP_RANGE

Das Betriebssytem ermöglicht das Weiterleiten von IP-Adressen mittels:

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

Wir erlauben Datenverkehr, der für unseren Webserver bestimmt ist, durch unsere Bridge zu leiten:

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

Umgekehrt soll Datenverkehr, der nach außen geht, mit der öffentlichen Adresse maskiert werden:

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

Datenverkehr von den dedizierten Ports in unsere Bridge ist erlaubt:

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

Ebenso eingehender Verkehr bereits bestehender Verbindungen:

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

Jeglicher Datenverkehr, der von unserer Bridge kommt ist zu erlauben, damit die Jails untereinander und mit der Außenwelt kommunizieren können:

iptables -A FORWARD -i br0 -j ACCEPT

Alle anderen Weiterleitungen werden dagegen gesperrt:

iptables -P FORWARD DROP

Optional können wir noch Datenverkehr erlauben, der von unserem Host kommt und auf den Datenbankserver zugreifen will:

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

Das ist nützlich, wenn wir etwa die Datenbank mit einem Service (wie phpPgAdmin) über eine separate IP-Adresse administrieren wollen.

Eine Vorlage für ein /etc/init.d-Skript findet man im Anhang. Vor dem Einsatz muss unbedingt mindestens die öffentliche IP-Adresse des Hosts in DESTINATION_IP gesetzt werden.

3. Erstellen der Dateisysteme

Wir erstellen ein Verzeichnis für die später in der Sandbox "gefangenen" Dateisysteme, gehen dort hinein

mkdir /jail

cd /jail

erzeugen ein minimales Dateisystem für den Webserver

debootstrap --arch=amd64 stable www

und kopieren es für den Datenbankserver

rsync -a www/ db

Letzteres ist weniger aufwändig als ein erneutes debootstrap, das man natürlich auch machen könnte.

Übrigens muss das Sandbox-Betriebssystem nicht unbedingt mit dem Wirtssystem übereinstimmen. Zum Zeitpunkt der Erstellung dieses Dokuments war mein Debian-7-Wirtssystem schon "oldstable", trotzdem funktioniert es mit "debootstrap ... stable ..." und man kann dann auch Software aus dem Stable-Zweig innerhalb des Jails installieren.

4. Einrichten der Dienste

4.1 Webserver

Zum Einrichten des Webservers starten wir firejail in dem zuvor erstellten Dateisystem

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

und installieren die Webserver-Software (in diesem Fall Apache):

apt-get update

apt-get install apache2

Nun gilt es noch, den Apache für die Umgebung, in der er laufen soll, geeignet zu konfigurieren. Wir können das innerhalb der mit firejail gestarteten Umgebung tun (dann bewegen wir uns in dem Verzeichnis /etc/apache2) oder von außerhalb (dann gehört die Konfiguration in das Verzeichnis /jail/www/etc/apache2); in beiden Fällen bearbeiten wir ein und dasselbe Verzeichnis, nur ist die Wurzel des Dateisystems jeweils eine andere.

Die nachstehende Tabelle zeigt die wesentlichen Eintragungen, die vorzunehmen sind:

Datei Änderung
ports.conf Listen-Direktive auf 10.10.20.10:80 bzw. 10.10.20.10:443 setzen
conf-available/servername.conf (Datei ggf. neu anlegen) Nur ein Eintrag: ServerName 10.10.20.10
sites-available/000-default.conf VirtualHost und ServerName auf 10.10.20.10:80 setzen
sites-available/default-ssl.conf VirtualHost und ServerName auf 10.10.20.10:443 setzen
envvars (optional) export APACHE_LOG_DIR=/var/local/log/apache2$SUFFIX (siehe unten)

Beim Starten des Jails wird das Verzeichnis /var/log an einen temporären Ort verschoben. Um die Apache-Logdateien permanent zu halten, können wir das Logverzeichnis wie oben beschrieben in der Konfigurationsdatei envvars ändern (hier in /var/local/log/apache2), müssen dann die entsprechende Verzeichnisstruktur aber manuell anlegen, also mkdir -p /var/local/log/apache2 (innerhalb des Jails).

Um den Servernamen zu aktivieren, setzen wir (innerhalb des Jails) den Befehl

a2enconf servername.conf

ab. Dann sind noch alle "normalen" Konfigurationen vorzunehmen, die für den Betrieb der Website nötig oder erwünscht sind.

Falls der Webserver schon läuft, sollten wir ihn mit

service apache2 stop

anhalten, bevor wir die firejail-Umgebung mit

exit

verlassen.

4.2 Datenbank

Für den Datenbankserver gehen wir analog vor, also zunächst

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

(jetzt mit db als chroot und Name) und dann (für PostgreSQL)

apt-get update
apt-get install postgresql

In der Datei /etc/postgresql/9.4/main/postgresql.conf (innerhalb des Jails, die Versionsnummer ist selbstverständlich anzupassen) sind folgende Einstellungen vorzunehmen:

Eintrag Wert Bemerkung
listen_addresses '10.10.20.20' Horchen auf unsere Bridge-IP.
logging_collector on Optional für Logging.
log_directory '/var/local/log/postgresql' Optional, um mit dem www-Logging konsistent zu sein.

Falls wir uns für das alternative log_directory entscheiden, müssen wir es auch erzeugen und die passenden Berechtigungen setzen (innerhalb des Jails):

mkdir -p /var/local/log/postgresql

chmod -R g-rwx,o-rwx /var/local/log/postgresql

chown -R postgres:postgres /var/local/log/postgresql

Natürlich sind auch hier alle sonstigen "normalen" Einstellungen für den Betrieb vorzunehmen.

Falls der Datenbankserver schon läuft, sollten wir ihn mit

service postgresql stop

anhalten, bevor wir die firejail-Umgebung mit

exit

verlassen.

Anmerkung: Trotz intensiver Versuche mit diversen Konfigurationen und umfangreichen Recherchen über mehrere Tage hinweg ist es mir nicht gelungen, einen MySQL-Server in einem Firejail aufzusetzen: Der Server startet zwar, beendet sich aber sofort wieder ohne brauchbare Fehlermeldungen oder Log-Dateieinträge zu hinterlassen. Daher habe ich mich schließlich für PostgreSQL entschieden.

5. Starten der Dienste

Zwecks Einrichtung konnten wir die Jails wie oben beschrieben starten. Um ihnen auch die Netzanbindung zu geben und die Server dabei automatisch im Hintergrund zu starten, verwenden wir die Kommandos

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" &

Die Option --private sorgt dafür, dass bestimmte Verzeichnisse des originalen Dateisystems wie /root oder /home nicht sichtbar sind, mit --net und --ip wird das Routing eingerichtet und der nachfolgende Befehl wird mit diesen Parametern im Jail ausgeführt: der Web- bzw. Datenbankserver wird gestartet und mit sleep inf wird erreicht, dass die Sandbox weiterläuft, auch wenn der Apache- oder PostgreSQL-Prozess beendet wird oder abstürzt (das mag für administrative Zwecke von Interesse sein). Anmerkung: sh -c ist hervorgehoben, weil es abweichend von anderen Beschreibungen bei mir ohne das nicht funktioniert.

Zum Auflisten der laufenden firejail-Prozesse dient:

firejail --list

Um eine laufende Sandbox zu betreten, rufen wir

firejail --join=www

oder

firejail --join=db

auf.

Falls wir die Server wieder stoppen wollen, geht das über

firejail --shutdown=www

firejail --shutdown=db

Skripte für init.d findet man im Anhang.

6. Schlussfolgerung

Mit der beschriebenen Vorgehensweise erhält man einen Webserver und einen Datenbankserver, die jeweils in einem abgeschotten Jail, jedoch auf der gleichen Maschine in einem internen IP-Bereich laufen.

Im Webserver-Jail kann man jetzt z. B. ein CMS wie Drupal oder Wordpress wie gewohnt installieren, muss dabei lediglich beachten, dass die Datenbank unter einer internen IP-Adresse (10.10.20.20) anzusprechen ist.

Durch die Abschottung erreicht man, dass etwa ein per SQL-Injektion eingedrungener Angreifer lediglich im Datenbankjail Schaden anrichten kann, aber nicht im Rest des Systems. Auch wenn solche Sicherheitsmaßnahmen nie perfekt sein können, haben wir zumindest dem Angreifer eine sehr hohe Hürde gesetzt.

AttachmentSize
Binary Data jailbridge.template.gz1.24 KB
Binary Data jail-www.gz472 bytes
Binary Data jail-db.gz471 bytes

Bereich: