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
2 Einrichten des Datenverkehrs
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.
Anhang | Größe |
---|---|
jailbridge.template.gz | 1.24 KB |
jail-www.gz | 472 Bytes |
jail-db.gz | 471 Bytes |