PoC BunkerWeb 1.6.17 : docker-compose sur Proxmox, déployé par Ansible

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>
This commit is contained in:
claude BotandClaude Opus 5.5 committed 2026-10-27 11:11:07 +01:00
commit 099584c95c
42 files changed
+4260

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# {{ ansible_managed }}
# N'autorise que le WAF (et les sources listées) à joindre les ports publiés
# des applis. Les règles vont dans DOCKER-USER, consultée par Docker avant
# ses propres règles ; --ctorigdstport filtre sur le port publié (avant DNAT).
set -eu
IFACE="{{ ansible_default_ipv4.interface }}"
PORTS="{{ apps_ports.values() | min }}:{{ apps_ports.values() | max }}"
iptables -N BWPOC 2>/dev/null || iptables -F BWPOC
{% for src in [waf_ip] + apps_direct_access_sources %}
iptables -A BWPOC -s {{ src }} -j RETURN
{% endfor %}
iptables -A BWPOC -j DROP
RULE="-i $IFACE -p tcp -m conntrack --ctstate NEW --ctorigdstport $PORTS -j BWPOC"
# shellcheck disable=SC2086
iptables -C DOCKER-USER $RULE 2>/dev/null || iptables -I DOCKER-USER $RULE
+56
View File
@@ -0,0 +1,56 @@
# {{ ansible_managed }}
# Sites de test du PoC BunkerWeb, mode {{ poc_mode }}
{% if poc_mode == 'split' %}
# Ports publiés sur {{ ansible_host }}, filtrés par la chaîne iptables BWPOC :
# seul le WAF ({{ waf_ip }}) peut les joindre.
{% else %}
# Aucun port publié : BunkerWeb joint les conteneurs par le réseau bw-services.
{% endif %}
services:
www:
image: {{ nginx_image }}
container_name: poc-www
restart: unless-stopped
read_only: true
tmpfs:
- /var/cache/nginx
- /run
- /tmp
volumes:
- ./www:/usr/share/nginx/html:ro
{% if poc_mode == 'split' %}
ports:
- "{{ ansible_host }}:{{ apps_ports.www }}:80"
{% endif %}
networks: [apps]
shop:
image: {{ juiceshop_image }}
container_name: poc-shop
restart: unless-stopped
{% if poc_mode == 'split' %}
ports:
- "{{ ansible_host }}:{{ apps_ports.shop }}:3000"
{% endif %}
networks: [apps]
api:
image: {{ httpbin_image }}
container_name: poc-api
restart: unless-stopped
read_only: true
{% if poc_mode == 'split' %}
ports:
- "{{ ansible_host }}:{{ apps_ports.api }}:8080"
{% endif %}
networks: [apps]
networks:
apps:
{% if poc_mode == 'single' %}
name: bw-services
external: true
{% else %}
name: poc-apps
{% endif %}
+26
View File
@@ -0,0 +1,26 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PoC BunkerWeb - contact</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 40rem; margin: 2rem auto; padding: 0 1rem; }
label, input, textarea, button { display: block; width: 100%; margin-bottom: .8rem; }
</style>
</head>
<body>
<h1>Contact</h1>
<p>Formulaire factice envoyé en GET : la vitrine est statique, c'est BunkerWeb qui
inspecte les paramètres. Essayez un message contenant
<code>&lt;script&gt;alert(1)&lt;/script&gt;</code> ou <code>' OR 1=1 --</code>.</p>
<form method="get" action="contact.html">
<label for="nom">Nom</label>
<input id="nom" name="nom">
<label for="message">Message</label>
<textarea id="message" name="message" rows="5"></textarea>
<button type="submit">Envoyer</button>
</form>
<p><a href="index.html">Retour</a></p>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PoC BunkerWeb - vitrine</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 52rem; margin: 2rem auto; padding: 0 1rem; line-height: 1.5; color: #222; }
h1 { color: #0b7a75; }
code { background: #f2f2f2; padding: .1rem .3rem; border-radius: 3px; }
table { border-collapse: collapse; width: 100%; }
td, th { border: 1px solid #ddd; padding: .4rem .6rem; text-align: left; }
</style>
</head>
<body>
<h1>PoC BunkerWeb</h1>
<p>Vous voyez cette page : vous avez passé le <strong>challenge antibot JavaScript</strong>
de BunkerWeb. Un <code>curl</code> sur cette même URL reste bloqué sur <code>/challenge</code>.</p>
<h2>Sites du PoC</h2>
<table>
<tr><th>Site</th><th>Application</th><th>Fonctionnalités mises en avant</th></tr>
<tr><td><a href="https://www.{{ poc_domain }}/">www</a></td><td>nginx statique</td><td>antibot, en-têtes de sécurité, HTTPS forcé</td></tr>
<tr><td><a href="https://shop.{{ poc_domain }}/">shop</a></td><td>OWASP Juice Shop</td><td>ModSecurity + OWASP CRS 4, websockets, User-Agents malveillants</td></tr>
<tr><td><a href="https://api.{{ poc_domain }}/get">api</a></td><td>go-httpbin</td><td>limitation de débit, méthodes, taille max, bad behavior, règle ModSecurity maison</td></tr>
<tr><td><a href="https://bwui.{{ poc_domain }}/">bwui</a></td><td>UI BunkerWeb</td><td>rapports, bannissements, réglages</td></tr>
</table>
<h2>Essais rapides dans le navigateur</h2>
<ul>
<li><a href="contact.html">Formulaire de contact</a> : saisissez <code>&lt;script&gt;alert(1)&lt;/script&gt;</code> et envoyez.</li>
<li><a href="/?file=../../../../etc/passwd">Traversée de répertoire</a> : réponse 403 attendue.</li>
</ul>
<p><small>Servi par {{ nginx_image }} derrière BunkerWeb {{ bunkerweb_version }}.</small></p>
</body>
</html>