So blockierst du Angriffe, bevor sie deine Webserver erreichen | BunkerWeb
Übersicht
Wer eigene Webanwendungen aus dem Internet erreichbar macht, möchte diese natürlich möglichst gut absichern. Genau hier kommt BunkerWeb ins Spiel. BunkerWeb kombiniert einen Reverse Proxy mit einer Web Application Firewall und kann dadurch eingehende Anfragen analysieren und potenziell schädliche Zugriffe blockieren.
In diesem Guide installieren wir BunkerWeb mit Docker Compose, richten die Weboberfläche ein und veröffentlichen unseren ersten Service. Anschließend testen wir mit einer absichtlich verwundbaren Anwendung, wie BunkerWeb auf verschiedene Angriffe reagiert.
Das Video zum Artikel hier: https://youtu.be/PDNMLmULm_M
DNS
Damit wir später über die gewünschten Domains auf die BunkerWeb-Weboberfläche und unsere Services zugreifen können, müssen die entsprechenden DNS-Einträge auf die IP-Adresse unseres BunkerWeb-Servers zeigen.
Wie genau die DNS-Einträge angelegt werden, unterscheidet sich je nach Provider. Grundsätzlich benötigen wir einen A-Record, der auf die IPv4-Adresse des Servers verweist.
Möchtet ihr eine komplette Domain inklusive aller Subdomains über BunkerWeb verwenden, könnt ihr beispielsweise einen Wildcard-A-Record für *.example.com erstellen. Dadurch werden alle Subdomains wie cloud.example.com oder app.example.com auf den BunkerWeb-Server aufgelöst. Beachtet dabei, dass der Wildcard-Eintrag die Hauptdomain example.com selbst nicht automatisch einschließt. Soll diese ebenfalls verwendet werden, benötigt sie einen eigenen DNS-Eintrag.
Wenn ihr nur einzelne Subdomains über BunkerWeb verwenden möchtet, könnt ihr stattdessen für jede gewünschte Subdomain einen eigenen A-Record erstellen.
Der DNS-Eintrag sorgt dabei lediglich dafür, dass die Domain auf die entsprechende IP-Adresse aufgelöst wird. Soll BunkerWeb aus dem Internet erreichbar sein, muss der Server zusätzlich von außen erreichbar sein. Je nach Netzwerk kann dafür beispielsweise eine öffentliche IP-Adresse und eine Portweiterleitung für die benötigten Ports erforderlich sein.
Installation
Es gibt verschiedene Möglichkeiten, BunkerWeb zu installieren. Ich bevorzuge den Weg mit Docker bzw. Docker Compose.
Docker
Als Erstes installieren wir Docker, falls das noch nicht geschehen ist, da wir BunkerWeb mit Docker betreiben möchten. Das können wir ganz einfach mit dem offiziellen Convenience Script von Docker machen.
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
Verzeichnis
Bevor wir fortfahren, müssen wir uns ein geeignetes Verzeichnis erstellen, wo wir BunkerWeb später laufen lassen wollen. Ich habe meine Docker-Compose Stacks immer gerne unter /opt, darunter erstelle ich einen neuen Ordner für BunkerWeb mit:
sudo mkdir /opt/bunkerweb
cd /opt/bunkerweb
Docker Compose
Das Docker-Compose-File für BunkerWeb ist recht umfangreich und beinhaltet alle Services, die BunkerWeb benötigt. Das offizielle Docker-Compose-Template findet ihr hier.
Unten findet ihr meine bereits angepasste Docker-Compose-Datei. Ich habe das Template genommen und folgende Änderungen vorgenommen:
- Volumes zu Bind-Mounts umgeändert, um alle Dateien zentral im BunkerWeb-Verzeichnis zu haben
- Zeitzone bei den Containern hinterlegt
- Syslog-NG Service konfiguriert, um später die Logs auch in der Web-GUI sehen zu können.
- TOTP-Variable hinzugefügt, um 2-Faktor für den Account aktivieren zu können.
Wenn ihr die Version verwenden wollt, müsst ihr nur oben in DATABASE_URI und die Variable MYSQL_PASSWORD im DB-Container mit dem gleichen sicheren Passwort versehen und für TOTP_ENCRYPTION_KEYS einen Key generieren und einfügen.
x-bw-env: &bw-env
# We use an anchor to avoid repeating the same settings for both services
API_WHITELIST_IP: "127.0.0.0/8 10.50.30.0/24" # Make sure to set the correct IP range so the scheduler can send the configuration to the instance
# Optional: set an API token and mirror it in both containers
API_TOKEN: ""
DATABASE_URI: "mariadb+pymysql://bunkerweb:<sicherespw>@bw-db:3306/db" # Remember to set a stronger password for the database
services:
bunkerweb:
# This is the name that will be used to identify the instance in the Scheduler
image: bunkerity/bunkerweb:1.6.14
ports:
- "80:8080/tcp"
- "443:8443/tcp"
- "443:8443/udp" # For QUIC / HTTP3 support
environment:
<<: *bw-env # We use the anchor to avoid repeating the same settings for all services
TZ: "Europe/Berlin"
restart: "unless-stopped"
networks:
- bw-universe
- bw-services
logging:
driver: syslog
options:
tag: "bunkerweb" # This will be the tag used by syslog-ng to create the log file
syslog-address: "udp://10.50.30.254:514" # This is the syslog-ng container address
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:1.6.14
environment:
<<: *bw-env
BUNKERWEB_INSTANCES: "bunkerweb" # Make sure to set the correct instance name
SERVER_NAME: ""
MULTISITE: "yes"
UI_HOST: "http://bw-ui:7000" # Change it if needed
USE_REDIS: "yes"
REDIS_HOST: "redis"
TZ: "Europe/Berlin"
volumes:
- ./bw-storage:/data # This is used to persist the cache and other data like the backups
restart: "unless-stopped"
networks:
- bw-universe
- bw-db
logging:
driver: syslog
options:
tag: "bw-scheduler" # This will be the tag used by syslog-ng to create the log file
syslog-address: "udp://10.50.30.254:514" # This is the syslog-ng container address
bw-ui:
image: bunkerity/bunkerweb-ui:1.6.14
environment:
<<: *bw-env
TOTP_ENCRYPTION_KEYS: "" # python3 -c "from passlib import totp; print(totp.generate_secret())"
TZ: "Europe/Berlin"
restart: "unless-stopped"
volumes:
- ./bw-logs:/var/log/bunkerweb # This is the volume used to store the logs
- ./bw-lib:/var/lib/bunkerweb # stored flask secret, totp etc
networks:
- bw-universe
- bw-db
logging:
driver: syslog
options:
tag: "bw-ui" # This will be the tag used by syslog-ng to create the log file
syslog-address: "udp://10.50.30.254:514" # This is the syslog-ng container address
bw-db:
image: mariadb:11
# We set the max allowed packet size to avoid issues with large queries
command: --max-allowed-packet=67108864
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
MYSQL_DATABASE: "db"
MYSQL_USER: "bunkerweb"
MYSQL_PASSWORD: "<sicherespw>" # Remember to set a stronger password for the database
TZ: "Europe/Berlin"
volumes:
- ./bw-data:/var/lib/mysql
restart: "unless-stopped"
networks:
- bw-db
bw-syslog:
image: balabit/syslog-ng:4.8.0
# image: lscr.io/linuxserver/syslog-ng:4.8.1-r1-ls147 # For aarch64 architecture
cap_add:
- NET_BIND_SERVICE # Bind to low ports
- NET_BROADCAST # Send broadcasts
- NET_RAW # Use raw sockets
- DAC_READ_SEARCH # Read files bypassing permissions
- DAC_OVERRIDE # Override file permissions
- CHOWN # Change ownership
- SYSLOG # Write to system logs
volumes:
- ./bw-logs:/var/log/bunkerweb # This is the volume used to store the logs
- ./syslog-ng.conf:/etc/syslog-ng/syslog-ng.conf # This is the syslog-ng configuration file
environment:
TZ: "Europe/Berlin"
networks:
bw-universe:
ipv4_address: 10.50.30.254 # Make sure to set the correct IP address
redis: # Redis service for the persistence of reports/bans/stats
image: redis:8-alpine
command: >
redis-server
--maxmemory 256mb
--maxmemory-policy volatile-lru
--save 60 1000
--appendonly yes
volumes:
- ./redis-data:/data
restart: "unless-stopped"
environment:
TZ: "Europe/Berlin"
networks:
- bw-universe
networks:
bw-universe:
name: bw-universe
ipam:
driver: default
config:
- subnet: 10.50.30.0/24 # Make sure to set the correct IP range so the scheduler can send the configuration to the instance
bw-services:
name: bw-services
bw-db:
name: bw-db
Syslog-NG
Wie erwähnt, benötigen wir Syslog-NG, um später die Logs auch in der Web-GUI im Reiter Logs sehen zu können. Den Container dafür haben wir oben in der Compose bereits mit drin, was wir noch brauchen, ist eine Syslog-NG-Konfigurationsdatei. Hierfür gibt es ebenfalls ein Template von BunkerWeb, was ihr so übernehmen könnt.
In der Compose haben wir angegeben, dass die Konfigurationsdatei mit dem Namen syslog-ng.conf mit im BunkerWeb-Verzeichnis liegt.
cd /opt/bunkerweb
sudo nano syslog-ng.conf
Dort fügen wir Folgendes ein:
@version: 4.8
# Source configuration to receive logs from Docker containers
source s_net {
udp(
ip("0.0.0.0")
);
};
# Template to format log messages
template t_imp {
template("$MSG\n");
template_escape(no);
};
# Destination configuration to write logs to dynamically named files
destination d_dyna_file {
file(
"/var/log/bunkerweb/${PROGRAM}.log"
template(t_imp)
owner("101")
group("101")
dir_owner("root")
dir_group("101")
perm(0440)
dir_perm(0770)
create_dirs(yes)
);
};
# Log path to direct logs to dynamically named files
log {
source(s_net);
destination(d_dyna_file);
};
STRG + O und anschließend STRG + X speichert und schließt das Ganze.
BunkerWeb Ordner
Da wir die Verzeichnisse oben auf Bind-Mounts umgestellt haben, müssen wir diese einmal vorher erstellen und mit den korrekten Berechtigungen versehen, damit BunkerWeb mit den Ordnern arbeiten kann. Das funktioniert ganz einfach mit den 3 Befehlen.
sudo mkdir -p /opt/bunkerweb/{bw-data,bw-lib,bw-logs,bw-storage}
sudo chown -R root:101 /opt/bunkerweb/{bw-data,bw-lib,bw-logs,bw-storage}
sudo chmod -R 770 /opt/bunkerweb/{bw-data,bw-lib,bw-logs,bw-storage}
Starten der Container
Wir haben alles, was wir benötigen, und können die Container jetzt starten, um mit der Einrichtung weiterzumachen.
cd /opt/bunkerweb
sudo docker compose up -d
Einrichtung
Nachdem die Container gestartet sind und alles initialisiert ist, können wir die Weboberfläche mittels FQDN (Domain) oder IP-Adresse erreichen. Wir geben als Pfad noch /setup mit an.
https://fqdn-oder-ip/setup
Auf der Seite angekommen, erstellen wir als Nächstes unseren Admin-Account.

Im nächsten Fenster konfigurieren wir einen Service für unsere BunkerWeb-Weboberfläche.

Im letzten Reiter des Setups können wir noch einmal alles überprüfen und dann auch schon deployen lassen.

Wir landen nun auf der Login-Seite und können uns mit unseren neu erstellten Credentials anmelden.

2-Faktor-Authentifizierung
Nach erstem Einloggen werden wir unten rechts direkt darauf hingewiesen, einen 2. Faktor zu hinterlegen. Das sollten wir bei solch einer wichtigen Anwendung auch unbedingt tun!

Dafür gehen wir oben rechts auf unseren Benutzer, damit wir zu unserem Profil gelangen. Dort angekommen, gehen wir auf den Reiter Security. Hier können wir jetzt den 2. Faktor hinterlegen.

Weboberfläche
Kommen wir jetzt also einmal dazu, uns eine Übersicht über die Weboberfläche zu verschaffen. Wir finden links unsere verschiedenen Menüpunkte, wo wir später unter anderem Services erstellen, Logs einsehen und Reports prüfen können.
In der Mitte haben wir Statistiken zu unseren Instanzen.
Wir gehen einmal die wichtigsten Reiter gemeinsam durch.

Instanzen
Hier haben wir alle BunkerWeb Hosts. Wenn wir bspw. ein Cluster aus mehreren hätten, würden wir hier alle aufgelistet bekommen.

Global Settings
In den Global Settings können wir, wie der Name sagt, Einstellungen global für alle Services definieren. Dabei können wir auch sämtliche Plugins "voreinstellen".

Services
Das Herz von BunkerWeb! Hier erstellen wir all unsere Web-Services, die wir über BunkerWeb laufen lassen wollen, aber dazu später mehr.

Configs
Im Reiter Configs können wir vorgefertigte Configs erstellen, die wir dann für unsere Services nutzen können. Von normalen Reverse-Proxy Settings bis hin zu CRS & MODSEC ist hier alles dabei.

Templates
Hier gibt es die Möglichkeit, Templates für Services zu erstellen, die dann beim Erstellen neuer Services ausgewählt werden können. Im Default bestehen hier schon drei Templates mit verschiedenen Empfindlichkeiten, eins für die UI und eins für API-Services.

Reports
Der Reiter wird später wichtig für uns und gibt uns Auskunft darüber, welche Zugriffe BunkerWeb auf unsere Services geblockt hat und warum.

Bans
Hier werden alle bereits gebannten IP-Adressen aufgelistet und können bei Bedarf entbannt werden.

Jobs
Im Reiter Jobs werden alle Jobs, die BunkerWeb so hat und ausführt, aufgelistet. Wenn nötig, können hier Jobs von Hand gestartet und gestoppt werden.

Logs
Ein weiterer sehr wichtiger Reiter. Dank der Syslog-NG Config können wir hier die Container Logs sehen. Falls mal ein Zugriff auf einen Service nicht wie gewünscht funktioniert, aber in den Reports auch kein Block steht, finden wir hier ziemlich sicher unsere Antwort für vorhandene Probleme.

Service Erstellen
Erstellen wir jetzt also einmal gemeinsam unseren ersten Service, den wir über BunkerWeb erreichbar machen. Hierbei gibt es verschiedene Möglichkeiten, wie wir die Anwendung erreichen.
Docker-Anwendung auf dem Host
Falls es sich um weitere Docker-Anwendungen auf demselben Host handelt, können wir dort einfach das bw-services Netzwerk mit einbinden und dann so die Anwendung in BunkerWeb erreichen und für die Außenwelt erreichbar machen. Das Vorgehen nochmal genauer erklärt findet ihr hier.
Für das Beispiel nehme ich eine einfache Demo-Webseite und passe die compose.yml entsprechend an.
services:
whoami:
image: traefik/whoami:latest
container_name: whoami
restart: unless-stopped
networks:
- bw-services
# kein ports: der Zugriff läuft ausschließlich über BunkerWeb
networks:
bw-services:
external: true
Anwendung auf anderem Host
Wenn es sich um eine Anwendung handelt, die auf einem anderen Host läuft, dann muss nur sichergestellt werden, dass BunkerWeb die Anwendung via dem entsprechenden Port erreicht. Das geben wir dann so bei der Erstellung als Backend-Service an.
Erstellen in BunkerWeb
Nachdem der Zugriff von BunkerWeb auf die Anwendung geklärt ist, können wir den Service anlegen. Auch hier gibt es verschiedene Möglichkeiten. Die einfachste dabei ist aber über die GUI.
Dafür navigieren wir zuerst einmal zum Reiter Services und klicken auf create new service.

Auf der Setup-Seite angekommen, wirkt es erstmal sehr erschlagen, als müssten wir total viel konfigurieren. Aber wenn wir das nicht wollen, müssen wir das auch nicht, denn hierfür haben wir ja die mitgelieferten Templates low, medium und high.
Je nach Anwendung können wir das oben im Dropdown auswählen und hinterlegen.

In dem Reiter Web service - Front Service bestimmen wir, wie es von außen aussehen soll. Wir hinterlegen also unsere gewünschte Domain (die auf den BunkerWeb-Server zeigen muss!) und stellen den Modus auf Block oder Detect.
Detect: BunkerWeb erkennt verdächtige Requests und protokolliert sie, blockiert sie aber nicht direkt.
Block: Erkannte Angriffe werden tatsächlich abgewehrt.
Der Rest auf der Seite kann beim Default belassen werden und wir können auf Next gehen.

Im zweiten Reiter jetzt die Backend-Seite, also wie BunkerWeb zu unserer Anwendung kommt. Hier müssen wir eigentlich nur den Host eingeben. Handelt es sich um einen via bw-services angebundenen Container, so gebt ihr den Container-Namen an. Bei einem anderen Host gebt ihr die URL ein, wie ihr es ohne Domain aufruft, also bspw. http://ip:port.

Der ganze Rest kann bei Bedarf angepasst werden, kann aber so erstmal beim Default belassen werden. Mit Klick auf Save oben rechts speichert ihr den Service.
Damit haben wir unseren ersten Service erstellt und können ihn jetzt erreichen.


BunkerWeb in Aktion
Bis hierhin haben wir BunkerWeb hauptsächlich wie einen normalen Reverse Proxy verwendet. Wir haben einen Service über eine eigene Domain erreichbar gemacht und BunkerWeb kümmert sich darum, die Anfragen an das entsprechende Backend weiterzuleiten.
BunkerWeb kann allerdings deutlich mehr. Durch die integrierte Web Application Firewall werden eingehende Anfragen analysiert und potenziell schädliche Zugriffe erkannt und je nach Konfiguration direkt blockiert.
Schauen wir uns also einmal praktisch an, was passiert, wenn jemand versucht, eine unserer Anwendungen anzugreifen. Dafür habe ich eine Instanz von DVWA aufgesetzt. Die Damn Vulnerable Web Application wurde absichtlich mit verschiedenen Sicherheitslücken entwickelt und eignet sich daher perfekt für unsere Tests.
Wir simulieren einige typische Angriffe und schauen anschließend, ob BunkerWeb diese erkennt, blockiert und welche Informationen wir dazu in den Reports finden.
SQL-Injection
Der Angreifer schmuggelt Datenbank-Befehle ins Eingabefeld, um die Datenbank auszutricksen und z. B. Passwörter auszulesen oder sich ohne Login einzuloggen.
Dafür nutzen wir eine Query und schauen, was passiert

Wir erhalten im Browser ein 403, was "Verboten" bedeutet.

In den BunkerWeb Reports wurde unser Angriff erkannt und geblockt.

XSS (Cross-Site-Scripting)
Der Angreifer schleust eigenen JavaScript-Code ein, der im Browser anderer Besucher ausgeführt wird, etwa um deren Login-Daten zu stehlen.

Auch hier erhalten wir ein 403 beim Klicken auf Submit.

In den Reports von BunkerWeb wurde der Angriff korrekt erkannt.

Command Injection
Der Angreifer hängt eigene Systembefehle an eine Eingabe an, die der Server ausführt. Im schlimmsten Fall übernimmt er den ganzen Server.

Auch hier gibt es ein 403.

In den Reports hat BunkerWeb dies ebenfalls erkannt.

Fazit
Damit haben wir BunkerWeb vollständig eingerichtet und unseren ersten Service darüber veröffentlicht. Im Vergleich zu einem klassischen Reverse Proxy ist die Einrichtung zwar etwas umfangreicher, dafür bekommen wir mit der integrierten WAF, den Reports und den zahlreichen Konfigurationsmöglichkeiten einige interessante Sicherheitsfunktionen dazu.
Besonders für öffentlich erreichbare selfhosted Anwendungen finde ich BunkerWeb daher spannend. Wie unsere Tests gezeigt haben, können typische Angriffe erkannt und blockiert werden, während wir über die Reports nachvollziehen können, was genau passiert ist. Natürlich ersetzt eine WAF keine sichere Konfiguration der eigentlichen Anwendung, sie bietet aber eine zusätzliche Sicherheitsschicht vor unseren Services.