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>
215 lines
12 KiB
Markdown
215 lines
12 KiB
Markdown
# PoC BunkerWeb sur Proxmox
|
||
|
||
Un environnement de test complet pour [BunkerWeb](https://www.bunkerweb.io/), 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`](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`](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](https://owasp.org/www-project-juice-shop/), appli volontairement vulnérable | ModSecurity + OWASP CRS 4 (SQLi, XSS, traversée), websockets, User-Agents malveillants |
|
||
| `api.<domaine>` | [go-httpbin](https://github.com/mccutchen/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-core` 2.16 ou plus, une paire de clés SSH.
|
||
|
||
```bash
|
||
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` :
|
||
|
||
1. [`inventory/hosts.yml`](inventory/hosts.yml) : IP du nœud Proxmox, IP et VMID des 3 VM, CPU, RAM, disque.
|
||
2. [`inventory/group_vars/all.yml`](inventory/group_vars/all.yml) : domaine, passerelle, DNS, stockage Proxmox, pont, VLAN, clé SSH publique.
|
||
3. [`inventory/group_vars/bunkerweb.yml`](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
|
||
|
||
```bash
|
||
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 :
|
||
|
||
```bash
|
||
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/`](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` :
|
||
|
||
```yaml
|
||
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) :
|
||
|
||
```bash
|
||
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` :
|
||
|
||
```bash
|
||
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 :
|
||
|
||
```yaml
|
||
# inventory/group_vars/all.yml
|
||
apps_direct_access_sources: [192.168.200.63]
|
||
```
|
||
|
||
```bash
|
||
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-agent` pour 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 `www` en `captcha` ou `cookie` dans `bunkerweb.yml`, relancez `make 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_confs` de type `MODSEC_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 bunkerweb` sur `bw-waf`, et l'audit ModSecurity dans `/var/log/bunkerweb/modsec_audit.log` du conteneur.
|
||
|
||
## Exploitation
|
||
|
||
```bash
|
||
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/>
|