Stack BunkerWeb (scheduler, UI, MariaDB, Redis), sites de test (nginx, Juice Shop, go-httpbin), filtrage DOCKER-USER, script de tests, README et schéma d'architecture. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PoC BunkerWeb sur Proxmox
Un environnement de test complet pour BunkerWeb, le reverse proxy WAF open source basé sur nginx, déployé en docker-compose sur des VM Proxmox, de bout en bout par Ansible.
L'objectif : voir concrètement ce que BunkerWeb bloque, laisse passer et journalise, sur des applis qui s'y prêtent, puis rejouer les mêmes attaques sans WAF pour comparer.
Ce que contient le PoC
| VM | Rôle | Conteneurs |
|---|---|---|
bw-waf |
Point d'entrée unique, ports 80/443 (TCP + UDP pour HTTP/3) | bunkerweb, bw-scheduler, bw-ui, bw-db (MariaDB 11), bw-redis |
bw-apps |
Sites de test, joignables uniquement depuis le WAF (iptables DOCKER-USER) |
poc-www (nginx), poc-shop (OWASP Juice Shop), poc-api (go-httpbin) |
bw-client |
Poste "attaquant" avec les outils et le script de tests | curl, sqlmap, nmap, ab, bwpoc-tests |
Versions épinglées dans inventory/group_vars/all.yml : BunkerWeb 1.6.17, Juice Shop v20.3.0, go-httpbin 2.26.0, nginx 1.30, Debian 13 (trixie) genericcloud.
Une variante tout-en-un (une seule VM) est fournie : inventory/single.yml. Le mode est détecté automatiquement : si WAF et applis sont le même hôte, BunkerWeb joint les applis par le réseau Docker bw-services et aucun port applicatif n'est publié.
Les sites de test
Chaque site met en avant un groupe de fonctionnalités :
| Site | Appli | Ce qu'on y teste |
|---|---|---|
www.<domaine> |
vitrine nginx statique (page d'accueil + formulaire de contact) | antibot JavaScript, en-têtes de sécurité, redirection HTTPS, XSS via formulaire dans le navigateur |
shop.<domaine> |
OWASP Juice Shop, appli volontairement vulnérable | ModSecurity + OWASP CRS 4 (SQLi, XSS, traversée), websockets, User-Agents malveillants |
api.<domaine> |
go-httpbin, API qui renvoie ce qu'elle reçoit | limitation de débit, méthodes autorisées, taille max du corps, bad behavior et bannissement, règle ModSecurity maison, compression, X-Forwarded-For |
bwui.<domaine> |
interface web de BunkerWeb | rapports d'attaques, bannissements, réglages, journaux |
Le domaine par défaut est bwpoc.internal (TLD réservé par l'ICANN pour l'usage privé). Les certificats sont auto-signés par BunkerWeb, site par site.
Prérequis
- Un nœud Proxmox VE 8 joignable en SSH root depuis le poste Ansible, avec accès Internet (image Debian, images Docker, listes noires communautaires).
- 3 IP libres sur le réseau des VM (1 en mode tout-en-un). Le playbook vérifie par ping qu'elles sont libres avant de créer les VM.
- Sur le poste Ansible :
ansible-core2.16 ou plus, une paire de clés SSH.
git clone git@gitea.vlab.bzh:vLab-BZH/bunkerwebpoc.git && cd bunkerwebpoc
make deps # installe community.docker et ansible.posix dans ./collections
Configuration
Tout ce qui dépend de votre infra est marqué A ADAPTER :
inventory/hosts.yml: IP du nœud Proxmox, IP et VMID des 3 VM, CPU, RAM, disque.inventory/group_vars/all.yml: domaine, passerelle, DNS, stockage Proxmox, pont, VLAN, clé SSH publique.inventory/group_vars/bunkerweb.yml: les réglages BunkerWeb, globaux et par site. C'est le fichier à modifier pour jouer avec les fonctionnalités.
Les mots de passe (MariaDB, Redis, admin de l'UI) sont générés au premier lancement dans credentials/ côté poste Ansible (ignoré par git). Pour les mettre sous ansible-vault, remplacez les lookup('password') de bunkerweb.yml par vos variables chiffrées.
Déploiement
make deploy # = ansible-playbook playbooks/site.yml
| Étape | Playbook | Ce qu'elle fait |
|---|---|---|
| 1 | 00-proxmox.yml |
télécharge l'image Debian 13 genericcloud, crée le modèle cloud-init (VMID 9100), clone les VM, injecte IP et clé SSH, agrandit les disques, démarre |
| 2 | 10-base.yml |
paquets de base, agent QEMU, résolution locale des noms du PoC vers le WAF, Docker CE depuis le dépôt officiel |
| 3 | 20-apps.yml |
stack des applis, filtrage DOCKER-USER (mode split) |
| 4 | 30-bunkerweb.yml |
stack BunkerWeb, attente qu'api réponde au travers du WAF, écriture de credentials/ACCES.txt |
| 5 | 40-client.yml |
outils d'attaque et script de tests sur bw-client |
Vous avez déjà vos VM (Debian 12 ou 13, accès SSH avec sudo) ? Renseignez-les dans l'inventaire et sautez l'étape 1 : make base apps waf client.
Pour relire les fichiers compose générés sans rien déployer :
ansible-playbook playbooks/render.yml # écrit build/split/... ou build/single/...
Un exemple de rendu (secrets remplacés par CHANGEME) est versionné dans examples/split/ (bunkerweb.compose.yml, apps.compose.yml, bwpoc-firewall.sh).
Accéder aux sites depuis votre poste
Les VM du PoC résolvent déjà les noms (bloc dans /etc/hosts). Sur votre poste, ajoutez la ligne suivante à /etc/hosts, ou déclarez les enregistrements dans votre DNS de labo :
192.168.200.61 www.bwpoc.internal shop.bwpoc.internal api.bwpoc.internal bwui.bwpoc.internal
Puis ouvrez https://bwui.bwpoc.internal/ (certificat auto-signé à accepter). Identifiants : make creds.
Comment BunkerWeb est configuré ici
Intégration Docker officielle (pas l'autoconf) : toute la configuration est portée par les variables d'environnement du bw-scheduler, qui génère la configuration nginx et la pousse à l'instance bunkerweb par son API (réseau bw-universe 10.20.30.0/24, seul autorisé par API_WHITELIST_IP). Ce choix permet de protéger des backends situés sur une autre VM, ce que les labels de l'autoconf ne savent pas faire.
MULTISITE=yes : chaque réglage peut être préfixé par le nom du site (api.bwpoc.internal_LIMIT_REQ_RATE=5r/s). Dans Ansible, il suffit de remplir le dictionnaire bw_sites :
bw_sites:
api:
upstream: api # clé de apps_ports, ou URL complète
settings:
ALLOWED_METHODS: "GET|POST|HEAD"
MAX_CLIENT_SIZE: "1m"
LIMIT_REQ_RATE: "5r/s"
custom_confs: # devient api.<domaine>_CUSTOM_CONF_MODSEC_poc-rule
MODSEC_poc-rule: >-
SecRule ARGS:poc "@streq attack" "id:10001,phase:2,deny,status:403,log,msg:'PoC'"
Les réglages posés par variables d'environnement apparaissent dans l'UI en lecture seule (méthode scheduler). Les modifications faites dans l'UI sont stockées en base et s'ajoutent par-dessus.
Redis est activé (USE_REDIS=yes) : bans, sessions et compteurs sont partagés, ce qui prépare l'ajout d'une seconde instance bunkerweb derrière un VIP (keepalived) sans changer le reste.
Tester les fonctionnalités
Depuis bw-client (ou n'importe quel poste avec curl et jq, le script utilise --resolve et n'a pas besoin de DNS) :
make tests # depuis le poste Ansible
ssh debian@192.168.200.63 bwpoc-tests # ou directement sur le client
Le script enchaîne une vingtaine de contrôles et affiche OK, KO ou INFO :
| Fonctionnalité | Réglage | Requête | Attendu |
|---|---|---|---|
| HTTPS forcé | AUTO_REDIRECT_HTTP_TO_HTTPS |
http://api/get |
301 vers https |
| Serveur par défaut fermé | DISABLE_DEFAULT_SERVER |
Host inconnu | connexion fermée (444) |
| En-têtes de sécurité | STRICT_TRANSPORT_SECURITY, CONTENT_SECURITY_POLICY... |
api/get |
en-têtes présents, Server masqué |
| Proxy transparent | USE_REVERSE_PROXY |
api/headers |
le backend voit X-Forwarded-For |
| Compression | USE_GZIP, USE_BROTLI |
api/html |
Content-Encoding: br ou gzip |
| Antibot | USE_ANTIBOT=javascript |
www/ en curl |
redirection vers /challenge |
| WAF SQLi | ModSecurity + CRS 4 | shop/rest/products/search?q=')) OR 1=1-- |
403 |
| WAF XSS | idem | shop/?q=<script>alert(1)</script> |
403 |
| WAF traversée | idem | shop/?file=../../../../etc/passwd |
403 |
| WAF injection de commande | idem | api/get?cmd=;cat /etc/passwd |
403 |
| Règle maison | CUSTOM_CONF_MODSEC |
api/get?poc=attack |
403 |
| User-Agent maison | BLACKLIST_USER_AGENT |
-A PoC-EvilBot |
403 |
| User-Agent communautaire | BLACKLIST_COMMUNITY_LISTS |
-A sqlmap/1.8 |
403 (info : dépend du téléchargement de la liste) |
| Méthodes | ALLOWED_METHODS |
PUT api/put |
405 |
| Taille du corps | MAX_CLIENT_SIZE=1m |
POST de 2 Mo | 413 |
| Limitation de débit | LIMIT_REQ_RATE=5r/s |
40 requêtes en parallèle | une partie en 429 |
| Bad behavior | BAD_BEHAVIOR_THRESHOLD=10 en 1 min |
12 × /status/404 |
IP bannie (403) sur api seulement |
Les derniers tests bannissent volontairement votre IP du site api pendant 5 minutes (bw_ban_time). Pour lever le ban : menu Bans de l'UI, ou sur bw-waf :
docker exec bw-scheduler bwcli unban 192.168.200.63
Avec et sans WAF
Pour mesurer l'apport du WAF, autorisez le client à joindre les applis en direct, puis rejouez les attaques :
# inventory/group_vars/all.yml
apps_direct_access_sources: [192.168.200.63]
make apps
ssh debian@192.168.200.63 bwpoc-tests --direct
La SQLi de Juice Shop renvoie alors la liste complète des produits (200) au lieu d'un 403, et le PUT passe.
Aller plus loin à la main
- sqlmap contre Juice Shop, d'abord avec son User-Agent par défaut (bloqué par la liste communautaire), puis avec
--random-agentpour voir le CRS prendre le relais :sqlmap -u "https://shop.bwpoc.internal/rest/products/search?q=test" --random-agent --batch - Antibot dans le navigateur : passez
wwwencaptchaoucookiedansbunkerweb.yml, relancezmake waf. - Faux positifs : naviguez dans Juice Shop (connexion, panier, avis). Ce que le CRS bloque à tort apparaît dans l'UI (Reports) avec l'ID de règle ; on l'exclut par un
custom_confsde typeMODSEC_CRS(exemple :SecRuleRemoveById 942100). - Mode détection seule :
MODSECURITY_SEC_RULE_ENGINE: "DetectionOnly"sur un site, pour observer avant de bloquer, comme on le ferait en production. - Journaux :
docker logs -f bunkerwebsurbw-waf, et l'audit ModSecurity dans/var/log/bunkerweb/modsec_audit.logdu conteneur.
Exploitation
make waf # réapplique la conf BunkerWeb après modification de bunkerweb.yml
make destroy # supprime les VM (confirmation demandée), modèle conservé
ansible-playbook playbooks/99-destroy.yml -e destroy_template=true # supprime aussi le modèle
Sur bw-waf, la stack vit dans /opt/bwpoc/bunkerweb (docker compose ps, logs, pull...). Sur bw-apps, dans /opt/bwpoc/apps.
Limites assumées du PoC
- Certificats auto-signés : pour du Let's Encrypt en labo fermé, passer par le challenge DNS (
AUTO_LETS_ENCRYPT,LETS_ENCRYPT_CHALLENGE=dns) avec votre fournisseur DNS. - Une seule instance BunkerWeb : pas de haute disponibilité, Redis est là pour la suite.
- Les fonctions PRO de BunkerWeb (anti DDoS, monitoring Prometheus, cache, rapports) ne sont pas couvertes.
- Le géoblocage (
BLACKLIST_COUNTRY) et les DNSBL ne se testent pas depuis des IP privées.
Arborescence
inventory/ hosts.yml (3 VM), single.yml (tout-en-un), group_vars/
playbooks/ 00 à 50 + site.yml, render.yml, 99-destroy.yml
roles/
proxmox_template/ modèle Debian 13 cloud-init via qm
proxmox_vm/ clonage, cloud-init, démarrage
common/ paquets, agent QEMU, /etc/hosts
docker/ Docker CE + compose (dépôt officiel)
apps/ compose des applis, vitrine, filtrage DOCKER-USER
bunkerweb/ compose BunkerWeb (template Jinja)
client/ outils et script de tests
tests/bwpoc-tests.sh batterie de tests fonctionnels
examples/split/ compose générés, pour lecture
docs/ schéma d'architecture (.excalidraw et .png)
Références
- Documentation BunkerWeb : https://docs.bunkerweb.io/latest/
- Fichiers d'intégration officiels (docker, MariaDB, UI) : https://github.com/bunkerity/bunkerweb/tree/v1.6.17/misc/integrations
- Liste des réglages, extraite des
plugin.json: https://github.com/bunkerity/bunkerweb/tree/v1.6.17/src/common/core - OWASP Core Rule Set : https://coreruleset.org/
