> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# مختبر Lab: اكتشف السيرفرات المتأثرة بثغرة CVE جديدة وأصلحها
- URL: https://arabroot.io/articles/مختبر-اكتشاف-السيرفرات-المتاثرة-بثغرة-cve-واصلاحها/
- Published: 2026-10-22T06:30:00.000Z
- Updated: 2026-10-26T08:07:55.000Z
- Description: فحصنا ثلاثة سيرفرات قديمة وسيرفراً محدثاً بأدوات Trivy وWazuh وسكربت تنبيه صغير، فظهر من يكتشف ثغرة الأسبوع ومن يفوته.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الأمن السيبراني, لينكس والسيرفرات, الاستضافة الذاتية, Wazuh

في مقال [إدارة الثغرات Vulnerability Management](https://arabroot.io/articles/%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AB%D8%BA%D8%B1%D8%A7%D8%AA-vulnerability-management-%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA%D9%83/) شرحنا لماذا يخدعك رقم الإصدار على التوزيعات، وكيف تمر الثغرة من نشرة المورد إلى سيرفرك، وما الدورة التي تحتاجها من الجرد Inventory حتى التحقق Verification. وفي هذا المقال سوف ننتقل إلى التطبيق، فنبني ثلاثة سيرفرات توقف تحديثها يوم 19 أكتوبر 2026، أي قبل يومين من نشرات Oracle وPHP وNode.js، وسيرفراً رابعاً محدثاً، ثم نسأل ثلاث أدوات مفتوحة المصدر السؤال نفسه: أي سيرفر متأثر بثغرات هذا الأسبوع؟ ثم نصلح ونعيد السؤال، ونقيس كم استغرقت كل أداة، وكم استهلكت من الذاكرة، وأين أخطأت.

📚

المواضيع في السلسلة: (1) إدارة الثغرات Vulnerability Management: المفهوم والدورة وأول 24 ساعة. (2) أدوات إدارة الثغرات مفتوحة المصدر: الجرد والمطابقة والتنبيه والمتابعة. (3) مختبر عملي: اكتشف السيرفرات المتأثرة بثغرة جديدة وأصلحها (الموضوع الحالي).

وسوف نتناول في هذا المقال ما يلي:

- بناء سيرفرات قديمة بشكل يمكن تكراره Reproducible باستخدام أرشيف snapshot.debian.org، بدلاً من البحث عن Docker Images قديمة.
- الفحص بدون وكيل Agentless مع Trivy على نظام ملفات كل سيرفر Root File System.
- الفحص المستمر بالوكيل Agent مع Wazuh، وكم يحتاج من الذاكرة RAM والقرص والوقت قبل أن يعطيك أول نتيجة صحيحة.
- التنبيه Alert عند ظهور ثغرة جديدة في منتج تستخدمه، ولماذا اكتفينا بسكربت Python صغير بدلاً من تثبيت OpenCVE.
- الإصلاح Patch ثم إعادة الفحص، ومتى تغلق النتيجة ومتى تبقى مفتوحة رغم الإصلاح.
- الإنذارات الخاطئة False Positives والثغرات التي فاتت الأدوات False Negatives كما ظهرت في المختبر.

## تصميم المختبر Lab: سيرفرات توقف تحديثها قبل النشرات

لنفرض أن لديك ثلاثة سيرفرات Debian 12 في الإنتاج، الأول يشغل تطبيق Java على OpenJDK 17 من مستودع التوزيعة، والثاني موقع PHP على Apache مع mod\_php، والثالث خدمة Node.js ثبتها الفريق من مستودع NodeSource لأن نسخة Debian قديمة، وآخر مرة نفذ أحد `apt upgrade` عليها كانت يوم 19 أكتوبر. ثم صدرت في 20 و21 أكتوبر 2026 نشرة Oracle للثغرة [CVE-2026-91403](https://www.oracle.com/security-alerts/alert-cve-2026-91403.html?ref=arabroot.io) في JSSE، ونشرتا PHP للثغرتين [CVE-2026-109618](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w?ref=arabroot.io) و[CVE-2026-109619](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp?ref=arabroot.io)، و[إصدارات Node.js الأمنية](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases?ref=arabroot.io) ومنها الثغرة CVE-2026-111497 في فك ضغط Brotli. وقد شرحنا تفاصيل هذه النشرات في المقال الأول من السلسلة، والسؤال هنا عملي: كيف تعرف بأداة وليس بالقراءة اليدوية أي سيرفر متأثر؟

### الطريقة البسيطة: نبحث عن Docker Image قديمة لكل برنامج

أول ما يخطر على البال لبناء سيرفر قديم هو أن نسحب Docker Image بوسم Tag قديم مثل `php:8.2.34-apache`، لكن هذه ال Images تبني PHP من الكود المصدري Source Code مباشرة، فلا توجد حزمة Package باسم php في قاعدة بيانات الحزم، وبالتالي لا تشبه سيرفراً حقيقياً ثبتت عليه PHP من التوزيعة، والأدوات التي تقرأ قائمة الحزم سوف تتعامل معها بشكل مختلف تماماً. والمشكلة الثانية أن مستودع Debian الحي لا يحتفظ إلا بآخر نسخة من كل حزمة، فلا يمكنك أن تطلب منه php8.2 كما كانت قبل أسبوع.

### الحل الأنسب: أرشيف snapshot.debian.org

يحتفظ مشروع Debian بأرشيف [snapshot.debian.org](https://snapshot.debian.org/?ref=arabroot.io) لكل حالة مرت بها المستودعات، فإذا وجهت ملف المصادر Sources إلى لحظة معينة حصلت على السيرفر كما كان في تلك اللحظة بالضبط، مع المستودع الأمني bookworm-security أيضاً. والملف التالي Dockerfile واحد للسيرفرات الأربعة، والمتغير ROLE يحدد ما يثبت في كل سيرفر:

```dockerfile
# One Dockerfile for the four lab servers; ROLE selects what gets installed.
# Old servers: Debian 12 frozen at 2026-10-19 (snapshot.debian.org), i.e. a server last updated before the October advisories.
# Patched server: the live Debian 12 repositories including bookworm-security.
FROM debian:bookworm-20261005-slim
ARG ROLE
ARG SNAPSHOT=20261019T000000Z
ARG NODE_VERSION=22.23.3-1nodesource1
ENV DEBIAN_FRONTEND=noninteractive
RUN if [ "$SNAPSHOT" != "live" ]; then \
      printf 'Types: deb\nURIs: http://snapshot.debian.org/archive/debian/%s\nSuites: bookworm bookworm-updates\nComponents: main\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nCheck-Valid-Until: no\n\nTypes: deb\nURIs: http://snapshot.debian.org/archive/debian-security/%s\nSuites: bookworm-security\nComponents: main\nSigned-By: /usr/share/keyrings/debian-archive-keyring.gpg\nCheck-Valid-Until: no\n' $SNAPSHOT $SNAPSHOT > /etc/apt/sources.list.d/debian.sources; fi \
 && apt-get update && apt-get -y upgrade && apt-get install -y --no-install-recommends curl gnupg ca-certificates procps lsb-release
# Wazuh agent repository (the agent is pointed at the manager at runtime)
RUN curl -fsSL https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --dearmor -o /usr/share/keyrings/wazuh.gpg \
 && echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" > /etc/apt/sources.list.d/wazuh.list \
 && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg \
 && echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
 && apt-get update && apt-get install -y wazuh-agent=4.14.9-1
RUN set -e; case "$ROLE" in \
  java) apt-get install -y --no-install-recommends openjdk-17-jre-headless ;; \
  php)  apt-get install -y --no-install-recommends apache2 libapache2-mod-php8.2 ;; \
  node) apt-get install -y nodejs=$NODE_VERSION ;; \
  all)  apt-get install -y --no-install-recommends openjdk-17-jre-headless apache2 libapache2-mod-php8.2 nodejs=$NODE_VERSION ;; \
  esac
COPY start.sh /start.sh
CMD ["sh", "/start.sh"]
```

والملف `start.sh` يوجه وكيل Wazuh إلى المدير Manager إذا أعطيناه عنوانه، ثم يشغل Apache إن وجد:

```bash
#!/bin/sh
# Point the agent at the manager (if given), start it, then keep the "service" running
if [ -n "$WAZUH_MANAGER" ]; then
  sed -i "s|<address>.*</address>|<address>$WAZUH_MANAGER</address>|" /var/ossec/etc/ossec.conf
  sed -i "s|<agent_name>.*</agent_name>||" /var/ossec/etc/ossec.conf
  /var/ossec/bin/wazuh-control start
fi
if command -v apache2ctl >/dev/null 2>&1; then apache2ctl start; fi
exec tail -f /dev/null
```

في الإعداد أعلاه لاحظ التالي:

- اخترنا ال Base Image بتاريخ 5 أكتوبر وليس 20 أكتوبر، والسبب أن ال Image الأحدث فيها حزم من bookworm-security أحدث من لحظة الأرشيف، فيرفض `apt` التثبيت برسالة `perl : Depends: perl-base (= 5.36.0-7+deb12u3) but 5.36.0-7+deb12u4 is to be installed`، وهذا ما ظهر لنا في المحاولة الأولى.
- السطر `Check-Valid-Until: no` ضروري، لأن ملف Release في الأرشيف انتهت صلاحيته الزمنية، و`apt` يرفض المستودعات منتهية الصلاحية بشكل افتراضي.
- السيرفر المحدث يبنى بالقيمة `SNAPSHOT=live`، أي من المستودعات الحية مع `apt-get -y upgrade`، ومعه NodeSource بالإصدار 22.23.4 المصلح.

والآن نبني السيرفرات الأربعة:

```bash
docker network create lab-net
for r in java php node; do docker build --build-arg ROLE=$r -t lab-srv-$r:old . ; done
docker build --build-arg ROLE=all --build-arg SNAPSHOT=live \
  --build-arg NODE_VERSION=22.23.4-1nodesource1 -t lab-srv-patched:new .
```

ثم نتأكد من النسخ المثبتة في كل سيرفر، لأن هذه النسخ هي ما سوف تقارنه الأدوات بقواعد بياناتها:

```bash
dpkg-query -W -f='${Package} ${Version}\n' openjdk-17-jre-headless libapache2-mod-php8.2 nodejs libbrotli1 ca-certificates
```

والمخرج من السيرفرات الأربعة كان كما يلي:

| السيرفر     | الحزمة الأساسية         | النسخة                                             | ca-certificates   |
| ----------- | ----------------------- | -------------------------------------------------- | ----------------- |
| srv-java    | openjdk-17-jre-headless | 17.0.20.1+1-1\~deb12u1                             | 20250419\~deb12u1 |
| srv-php     | libapache2-mod-php8.2   | 8.2.34-1\~deb12u1                                  | 20250419\~deb12u1 |
| srv-node    | nodejs (NodeSource)     | 22.23.3-1nodesource1                               | 20250419\~deb12u1 |
| srv-patched | الثلاث معاً             | 17.0.20.1 و8.2.35-1\~deb12u1 و22.23.4-1nodesource1 | 20250419\~deb12u2 |

لاحظ أن السيرفر المحدث ما زال على OpenJDK 17.0.20.1، والسبب أن Debian 12 لم تنشر حتى يوم التجربة حزمة openjdk-17 جديدة، فمتتبع Debian يعرض [CVE-2026-91403](https://security-tracker.debian.org/tracker/CVE-2026-91403?ref=arabroot.io) على أنها مصلحة في sid فقط. وسوف نعود لهذه النقطة لأنها أهم ما ظهر في المختبر.

والشكل التالي يبين المختبر كاملاً، والطرق الثلاث التي سوف نستخدمها:

![أربعة سيرفرات Debian 12 وثلاث طرق للاكتشاف: وكيل Wazuh وفحص Trivy للملفات وسكربت تنبيه يسأل OSV وNVD ونشرات الموردين](https://arabroot.io/content/images/2026/10/vuln-lab-01-lab.webp)

المختبر Lab: ثلاثة سيرفرات قديمة وسيرفر محدث، وثلاث طرق لاكتشاف الثغرة

## الفحص بدون وكيل Agentless مع Trivy rootfs

نبدأ بالأداة الأخف، فأداة [Trivy](https://github.com/aquasecurity/trivy?ref=arabroot.io) التي استخدمناها في [فحص ال Docker Images](https://arabroot.io/articles/%D9%81%D8%AD%D8%B5-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-%D9%83%D9%83%D9%88%D8%AF-%D9%85%D8%B9-trivy/) تستطيع أيضاً أن تفحص نظام ملفات كامل لسيرفر بالأمر [trivy rootfs](https://trivy.dev/docs/v0.76/guide/target/rootfs/?ref=arabroot.io)، فتقرأ قاعدة بيانات الحزم `/var/lib/dpkg/status` وملفات اللغات مثل `package-lock.json` وملفات JAR، ثم تقارنها بقاعدتها. وعلى سيرفر حقيقي تشغلها على المسار `/` نفسه أو على نسخة من القرص، وفي المختبر نأخذ نسخة من نظام ملفات كل Container بالأمر `docker export` إلى Volume:

```bash
docker volume create lab-rootfs
for r in java php node patched; do
  docker export srv-$r | docker run -i --rm -v lab-rootfs:/r alpine:3.22 \
    sh -c "mkdir -p /r/$r && tar -x -C /r/$r"
done
```

ثم نفحص كل سيرفر، ونحفظ قاعدة Trivy في Volume حتى لا تنزل في كل مرة:

```bash
docker run --rm -v lab-rootfs:/r:ro -v trivy-cache:/root/.cache aquasec/trivy:0.76.1 \
  rootfs --scanners vuln --format json -o /dev/stdout /r/php > trivy-php.json
```

واستغرق الفحص الأول دقيقتين و11 ثانية، أغلبها تنزيل قاعدة الثغرات التي كان تاريخ تحديثها `2026-10-22 13:15 UTC`، أي بعد نشرة PHP بنحو 37 ساعة وبعد نشر سجلي CVE بنحو 23 ساعة. أما الفحوص التالية فاستغرقت بين 6 و10 ثوان لكل سيرفر.

### ماذا يحدث إذا وجد Trivy ملفات JAR؟

عند فحص srv-java لأول مرة توقف Trivy بعد ست دقائق بهذا الخطأ:

```text
INFO	[javadb] Downloading Java DB...
INFO	[javadb] Downloading artifact...	repo="mirror.gcr.io/aquasec/trivy-java-db:1"
WARN	Provide a higher timeout value, see https://trivy.dev/docs/v0.76/guide/references/troubleshooting#timeout
FATAL	Fatal error	run error: rootfs scan error: scan error: scan failed: failed analysis: post analysis error: post analysis error: walk dir error: context deadline exceeded
```

والسبب أن Trivy وجد ملفات JAR فقرر تنزيل قاعدة Java الخاصة به وحجمها 941 MiB، واستهلك التنزيل المهلة الافتراضية وهي خمس دقائق. والحل أن ترفع المهلة بالخيار `--timeout 15m` في أول تشغيل، أو أن تنزل القاعدتين مسبقاً في خطوة منفصلة، وبعد ذلك استغرق فحص srv-java ست ثوان فقط مع الخيار `--skip-java-db-update`.

### النتيجة: من المتأثر بثغرات Vulnerabilities هذا الأسبوع؟

عدد النتائج الكامل لا يفيدك في يوم نشرة حرجة، فقد أعطى Trivy بين 497 و751 نتيجة لكل سيرفر، أغلبها LOW وMEDIUM بلا إصلاح. لذلك نستخدم قالباً Template يطبع سطراً واحداً لكل نتيجة ثم نبحث عن ثغرات هذا الأسبوع فقط، والقالب يجب أن ينتهي اسمه بالامتداد `.tpl` وإلا رفضه Trivy برسالة `template file must have .tpl extension`:

```text
{{- range . }}{{- range .Vulnerabilities }}{{ .PkgName }} {{ .InstalledVersion }} {{ .VulnerabilityID }} {{ .Severity }} {{ .Status }} {{ .FixedVersion }}
{{ end }}{{ end -}}
```

```bash
for r in java php node patched; do
  echo "== $r"
  docker run --rm -v lab-rootfs:/r:ro -v lab-files:/f:ro -v trivy-cache:/root/.cache aquasec/trivy:0.76.1 \
    rootfs --quiet --skip-db-update --skip-java-db-update --scanners vuln \
    --format template --template @/f/short.tpl /r/$r \
    | grep -E "CVE-2026-(91403|109618|109619|111497) " | sort
done
```

والمخرج سوف يكون كما يلي:

```text
== java
libbrotli1 1.0.9-2+b6 CVE-2026-111497 UNKNOWN affected
openjdk-17-jre-headless 17.0.20.1+1-1~deb12u1 CVE-2026-91403 UNKNOWN affected
== php
libapache2-mod-php8.2 8.2.34-1~deb12u1 CVE-2026-109618 CRITICAL fixed 8.2.35-1~deb12u1
libapache2-mod-php8.2 8.2.34-1~deb12u1 CVE-2026-109619 CRITICAL fixed 8.2.35-1~deb12u1
libbrotli1 1.0.9-2+b6 CVE-2026-111497 UNKNOWN affected
php8.2-cli 8.2.34-1~deb12u1 CVE-2026-109618 CRITICAL fixed 8.2.35-1~deb12u1
php8.2-cli 8.2.34-1~deb12u1 CVE-2026-109619 CRITICAL fixed 8.2.35-1~deb12u1
php8.2-common 8.2.34-1~deb12u1 CVE-2026-109618 CRITICAL fixed 8.2.35-1~deb12u1
php8.2-common 8.2.34-1~deb12u1 CVE-2026-109619 CRITICAL fixed 8.2.35-1~deb12u1
php8.2-opcache 8.2.34-1~deb12u1 CVE-2026-109618 CRITICAL fixed 8.2.35-1~deb12u1
php8.2-opcache 8.2.34-1~deb12u1 CVE-2026-109619 CRITICAL fixed 8.2.35-1~deb12u1
php8.2-readline 8.2.34-1~deb12u1 CVE-2026-109618 CRITICAL fixed 8.2.35-1~deb12u1
php8.2-readline 8.2.34-1~deb12u1 CVE-2026-109619 CRITICAL fixed 8.2.35-1~deb12u1
== node
libbrotli1 1.0.9-2+b6 CVE-2026-111497 UNKNOWN affected
== patched
libbrotli1 1.0.9-2+b6 CVE-2026-111497 UNKNOWN affected
openjdk-17-jre-headless 17.0.20.1+1-1~deb12u1 CVE-2026-91403 UNKNOWN affected 
```

في المخرج أعلاه لاحظ التالي:

- **PHP كان دقيقاً تماماً:** خمس حزم على srv-php بحالة `fixed` ومعها النسخة المصلحة 8.2.35-1\~deb12u1 التي نشرتها Debian في [DLA-4848-1](https://security-tracker.debian.org/tracker/DLA-4848-1?ref=arabroot.io)، ولا شيء من PHP على srv-patched. وهذا هو السطر الذي تريده من أي أداة: الحزمة والنسخة المثبتة والنسخة التي تصلحها.
- **Java على السيرفر المحدث ما زال «affected»:** لأن Debian لم تصدر openjdk-17 جديدة، فيبقى السطر نفسه على srv-java وsrv-patched، وسوف نرى في قسم الإصلاح أن هذا السطر إنذار خاطئ False Positive على سيرفر حدثت فيه حزمة الشهادات.
- **Node.js غائب تماماً عن srv-node:** الحزمة `nodejs 22.23.3-1nodesource1` متأثرة بحسب نشرة Node.js، لكن Trivy لم يذكرها بأي ثغرة من ثغرات أكتوبر، وهذه ثغرة فاتت الأداة False Negative كاملة.
- **libbrotli1 ظهرت على السيرفرات الأربعة:** وهي مكتبة النظام التي تستخدمها curl وغيرها، أما Node من NodeSource فلا يستخدمها، وهذا ما سوف نتحقق منه الآن.

### لماذا لم ير Trivy ثغرة Node.js؟

قد يتساءل البعض: إذا كان Trivy يعرف CVE-2026-111497 ويعرضها على libbrotli1، فلماذا لا يعرضها على nodejs؟ والإجابة أن Trivy يطابق حزم Debian ببيانات Debian، ومتتبع Debian يضع nodejs لهذه الثغرة في حالة «unimportant» لأن نسخة Debian من Node تستخدم مكتبة النظام، أما الحزمة التي ثبتناها فليست من Debian أصلاً وإنما من NodeSource، وهي تحمل نسخة Brotli مدمجة داخل الملف التنفيذي، والأمر التالي يبين ذلك:

```bash
ldd /usr/bin/node | grep -i -E "brotli|ssl|z\."
node -v
node -p "process.versions.brotli + \" openssl \" + process.versions.openssl"
```

والمخرج:

```text
v22.23.3
1.1.0 openssl 3.5.8
```

فلا يوجد ربط بأي مكتبة brotli أو ssl من النظام، وBrotli المستخدمة هي 1.1.0 المدمجة، وهي من النسخ المتأثرة بحسب [سجل CVE](https://www.cve.org/CVERecord?id=CVE-2026-111497&ref=arabroot.io) (من 1.1.0 حتى ما قبل 1.1.1). وبالتالي فالأداة رأت المكتبة غير المستخدمة وفاتها البرنامج المتأثر فعلاً، وهذا يحدث مع كل برنامج تثبته من مستودع خارجي يحمل مكتباته معه، مثل NodeSource وحزم الموردين التجارية.

### وماذا عن Java المثبت من ملف مضغوط Archive؟

ولو كان تطبيق Java يعمل على Eclipse Temurin المثبت في `/opt/java/openjdk` كما في ال Docker Image الرسمية `eclipse-temurin:17.0.20.1_1-jre-noble`، فإن النتيجة أسوأ، فقد فحصناها بالطريقة نفسها:

```text
INFO	Detected OS	family="ubuntu" version="24.04"
INFO	[ubuntu] Detecting vulnerabilities...	os_version="24.04" pkg_num=136
INFO	Number of language-specific files	num=0
```

فأعطى 31 نتيجة كلها لحزم Ubuntu، ولا شيء عن Java 17.0.20.1 مع أنها متأثرة بثغرات [نشرة OpenJDK في 20 أكتوبر](https://openjdk.org/groups/vulnerability/advisories/2026-10-20?ref=arabroot.io) وبالثغرة CVE-2026-91403، والسبب أن Java هنا ملفات في مجلد وليست حزمة مسجلة في dpkg، فلا يراها أي فاحص يعتمد على قاعدة بيانات الحزم. ولذلك فالجرد Inventory الذي تبنيه بنفسك (ما الذي ثبتناه خارج مدير الحزم؟) جزء لا تغني عنه أي أداة.

## الفحص المستمر بالوكيل Agent مع Wazuh

الفحص بأداة مثل Trivy صورة لحظية Snapshot، فإذا نسيت تشغيله أو تغيرت الحزم بعده فلن تعرف. أما [كشف الثغرات في Wazuh](https://documentation.wazuh.com/current/user-manual/capabilities/vulnerability-detection/index.html?ref=arabroot.io) فيعمل بشكل مختلف، فالوكيل Agent على كل سيرفر يجمع قائمة الحزم بوحدة Syscollector ويرسلها إلى المدير Manager، والمدير يقارنها بقاعدة Wazuh CTI ويحفظ النتيجة في الفهرس Indexer، وعندما تحدث حزمة على السيرفر تتغير القائمة فتغلق النتيجة وحدها. والسؤال الذي يهم من يدير عدداً قليلاً من السيرفرات هو كم يكلف هذا التشغيل المستمر.

### التثبيت على Docker في عقدة واحدة Single Node

نستخدم المستودع الرسمي [wazuh-docker](https://documentation.wazuh.com/current/deployment-options/docker/wazuh-container.html?ref=arabroot.io) بوسم الإصدار نفسه، ثم ننشئ الشهادات Certificates التي تتواصل بها المكونات الثلاث:

```bash
git clone -b v4.14.9 --depth 1 https://github.com/wazuh/wazuh-docker.git
cd wazuh-docker/single-node
docker compose -f generate-indexer-certs.yml run --rm generator
```

وقبل التشغيل غيرنا ثلاثة أشياء في `docker-compose.yml`، فحذفنا نشر المنافذ 1514 و1515 و514 و55000 و9200 على السيرفر المضيف لأن الوكلاء في الشبكة نفسها ولا حاجة لفتحها، وربطنا لوحة التحكم Dashboard بالعنوان المحلي فقط، وأضفنا الشبكة المشتركة مع السيرفرات:

```yaml
  wazuh.dashboard:
    ports:
      - "127.0.0.1:8443:5601"

networks:
  default:
    name: lab-net
    external: true
```

🛑

الملف الرسمي يضع كلمات مرور ثابتة مثل `SecretPassword` للفهرس و`MyS3cr37P450r.*-` لواجهة API، وهي مناسبة لمختبر معزول فقط. قبل أي تشغيل حقيقي غيرها كما في [دليل Wazuh على Docker](https://documentation.wazuh.com/current/deployment-options/docker/wazuh-container.html?ref=arabroot.io)، ولا تنشر المنافذ 9200 و55000 على الإنترنت.

```bash
docker compose up -d
```

ثم نشغل السيرفرات الأربعة ونعطي كلاً منها عنوان المدير، فيسجل الوكيل نفسه عبر المنفذ 1515 ثم يرسل بياناته عبر 1514:

```bash
for r in java php node; do
  docker run -d --name srv-$r --hostname srv-$r --network lab-net -e WAZUH_MANAGER=wazuh.manager lab-srv-$r:old
done
docker run -d --name srv-patched --hostname srv-patched --network lab-net -e WAZUH_MANAGER=wazuh.manager lab-srv-patched:new
docker exec single-node-wazuh.manager-1 /var/ossec/bin/agent_control -l
```

```text
Wazuh agent_control. List of available agents:
   ID: 000, Name: wazuh.manager (server), IP: 127.0.0.1, Active/Local
   ID: 001, Name: srv-patched, IP: any, Active
   ID: 002, Name: srv-java, IP: any, Active
   ID: 003, Name: srv-php, IP: any, Active
   ID: 004, Name: srv-node, IP: any, Active
```

وبعد التسجيل بدأت النتائج تظهر في لوحة التحكم، والصورة التالية من صفحة Vulnerability Detection بعد نحو عشر دقائق من تشغيل الوكلاء:

![لوحة Vulnerability Detection في Wazuh تعرض 374 نتيجة على أربعة وكلاء، منها 30 Critical و156 High](https://arabroot.io/content/images/2026/10/vuln-lab-03-wazuh-dashboard.webp)

لوحة كشف الثغرات في Wazuh: 374 نتيجة على الوكلاء الأربعة، وليس فيها أي ثغرة من ثغرات أكتوبر

### ماذا وجد Wazuh؟ تأخر قاعدة البيانات Feed Lag

للوهلة الأولى تبدو النتيجة مقنعة، فهي 374 نتيجة موزعة على السيرفرات، 128 منها على srv-patched و105 على srv-php و75 على srv-node و66 على srv-java، و30 منها Critical. لكن عندما سألنا الفهرس Indexer مباشرة عن الثغرات الأربع لم نجد سطراً واحداً، والاستعلام التالي يبين ذلك:

```bash
docker exec single-node-wazuh.indexer-1 curl -sk -u admin:SecretPassword \
  -H 'Content-Type: application/json' \
  "https://localhost:9200/wazuh-states-vulnerabilities-*/_search" \
  -d '{"size":100,"query":{"bool":{"should":[{"terms":{"vulnerability.id":["CVE-2026-91403","CVE-2026-109618","CVE-2026-109619","CVE-2026-111497"]}}]}}}'
```

فكان عدد النتائج صفراً، ثم سألنا عن أحدث تاريخ نشر `vulnerability.published_at` بين كل النتائج بتجميع max، فكان `2026-09-22T17:17:32.000Z`، أي أن Wazuh كان يطابق السيرفرات بقاعدة عمرها شهر. والسبب يظهر في سجل المدير `ossec.log`:

```text
2026/10/22 14:52:50 wazuh-modulesd:vulnerability-scanner: INFO: Starting database file decompression.
2026/10/22 15:02:58 wazuh-modulesd:vulnerability-scanner: INFO: Database decompression finished.
2026/10/22 15:02:59 wazuh-modulesd:vulnerability-scanner: INFO: Vulnerability scanner module started.
2026/10/22 15:13:00 wazuh-modulesd:vulnerability-scanner: INFO: Initiating update feed process.
```

في السجل أعلاه لاحظ التالي:

- المدير يأتي بقاعدة ثغرات مضغوطة داخل ال Image، واستغرق فكها عشر دقائق قبل أن يبدأ أي فحص، وهذه القاعدة هي التي أعطت النتائج الأولى.
- بعد عشر دقائق أخرى بدأ تحديث القاعدة من [خدمة Wazuh CTI](https://cti.wazuh.com/vulnerabilities/cves/CVE-2026-109618?ref=arabroot.io)، فنزل 104 ملفات تحديث Offsets، وبقي المدير يعالجها بين معالج Core واحد ومعالج ونصف حتى نحو 16:07، أي قرابة ساعة كاملة.
- حتى الساعة 16:17، أي بعد ثلاث ساعات ونصف من التشغيل، لم تظهر أي ثغرة من الأربع على أي سيرفر، مع أن صفحة خدمة CTI العامة كانت تعرض CVE-2026-109618 وCVE-2026-91403 في ذلك اليوم، ولم تكن تعرض CVE-2026-111497 بعد.

وقد يتساءل البعض: هل المشكلة في Wazuh نفسه؟ والإجابة أن هذا هو السلوك الطبيعي لأي أداة بقاعدة بيانات خاصة بها، فالثغرة تمر من نشرة المورد إلى سجل CVE ثم إلى مصادر الأداة ثم إلى قاعدتها المحلية ثم تنتظر دورة الفحص التالية، و[توثيق Wazuh](https://documentation.wazuh.com/current/user-manual/capabilities/vulnerability-detection/how-it-works.html?ref=arabroot.io) يقول إن الجرد يرسل بشكل دوري دون أن يحدد مدة ثابتة للتحديث. والخطأ الحقيقي أن تفترض أن اللوحة الخضراء تعني أنك غير متأثر في يوم النشرة نفسه. وقد أوقفنا المختبر بعد ذلك لأن قرص السيرفر المضيف امتلأ، فلم نقس الوقت الذي ظهرت فيه الثغرات في النهاية، وهذا أيضاً درس عن الموارد كما سيأتي.

### كم يكلف Wazuh من الموارد Resources؟

هذه الأرقام من الأمر `docker stats` ومن حجم المجلدات داخل ال Container خلال التجربة، مع أربعة وكلاء فقط:

| المكون                      | الذاكرة RAM                                                           | القرص Disk                                                          |
| --------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------- |
| المدير wazuh.manager        | 638 MiB عند البدء، ثم 1.3 إلى 1.7 GiB أثناء تحديث القاعدة، ثم 2.7 GiB | قاعدة الثغرات /var/ossec/queue/vd بحجم 11 GB، وملفات التحديث 977 MB |
| الفهرس wazuh.indexer        | بين 1.42 و1.51 GiB (الإعداد \-Xms1g -Xmx1g)                           | ال Image بحجم 2.56 GB                                               |
| لوحة التحكم wazuh.dashboard | بين 186 و230 MiB                                                      | ال Image بحجم 2.14 GB                                               |
| كل وكيل Agent               | بين 35 و85 MiB لل Container كاملاً                                    | صغير                                                                |

أي أن العقدة الواحدة احتاجت نحو 4.4 GiB من الذاكرة وأكثر من 16 GB من القرص قبل أن تخزن أي تنبيه، وهذا يتفق مع [متطلبات Wazuh الرسمية](https://documentation.wazuh.com/current/quickstart.html?ref=arabroot.io) التي تطلب 4 vCPU و8 GiB من الذاكرة و50 GB من القرص لعدد يصل إلى 25 وكيلاً. لذلك لا تقم بتشغيل Wazuh على سيرفر صغير بجانب تطبيقاتك، والسبب أن تحديث القاعدة وحده يستهلك معالجاً كاملاً لساعة، وخصص له سيرفراً أو آلة افتراضية مستقلة.

## التنبيه Alert عند ظهور ثغرة جديدة

الأداتان السابقتان تجيبان عن سؤال «أي سيرفر متأثر بما أعرفه؟»، أما السؤال الذي سأله صاحب السيرفر في بداية المقال فهو «كيف أعرف أن ثغرة جديدة ظهرت في شيء أستخدمه؟»، وهذا يحتاج اشتراكاً Subscription في المنتجات التي تستخدمها، وتنبيهاً يصلك عند ظهور ثغرة جديدة فيها.

### لماذا لم نثبت OpenCVE في المختبر؟

الأداة المعروفة لهذا الغرض هي [OpenCVE](https://docs.opencve.io/?ref=arabroot.io)، وفكرتها كما في [توثيقها](https://docs.opencve.io/concepts/core/?ref=arabroot.io) أن تنشئ مشروعاً Project وتشترك فيه بالموردين Vendors والمنتجات Products، ثم تضيف [أتمتة Automation](https://docs.opencve.io/guides/automations/?ref=arabroot.io) تعمل كل ساعة وترسل تنبيهاً بالبريد أو Slack أو Webhook عندما تدخل ثغرة جديدة إلى المشروع أو يتغير تقييمها أو تضاف إلى قائمة KEV. وقد بدأنا تثبيت الإصدار [3.1.0](https://github.com/opencve/opencve/releases/tag/v3.1.0?ref=arabroot.io) فعلاً، لكننا توقفنا لسببين:

- **الحجم:** النسخة المستضافة ذاتياً تحتاج خمسة مستودعات Git للبيانات، وحتى مع النسخ السطحي Shallow Clone كان حجمها نحو 15.6 GB (opencve-kb بحجم 5.1 GB وcvelistV5 بحجم 4.4 GB وopencve-nvd بحجم 3.9 GB وvulnrichment بحجم 1.8 GB وopencve-redhat بحجم 423 MB)، ثم قاعدة PostgreSQL التي يستورد إليها كل الثغرات، وتشغيل Airflow مع Redis وCelery، وقد استغرق بناء ال Images وحده 13 دقيقة. وفي يوم التجربة امتلأ قرص السيرفر المضيف مع Wazuh فتوقف Docker عن الاستجابة، فقررنا ألا نشغلها.
- **الرخصة:** OpenCVE منشورة برخصة [Business Source License 1.1](https://github.com/opencve/opencve/blob/master/LICENSE?ref=arabroot.io)، وهي ليست رخصة مفتوحة المصدر، فهي تسمح بالاستخدام الداخلي وتمنع تقديم الأداة خدمة مراقبة وتنبيه تجارية لطرف ثالث، وتتحول إلى Apache 2.0 بعد تاريخ التحويل المذكور في الملف. وهذا مناسب لفريق يراقب سيرفراته، لكن عليك أن تعرفه قبل أن تبني عليها خدمة لعملائك.

وإذا كان عدد المنتجات التي تستخدمها قليلاً فإن الفكرة نفسها يمكن تنفيذها بسكربت صغير يسأل المصادر العامة مباشرة، وهذا ما سوف نفعله الآن.

### سكربت تنبيه صغير بلغة Python

السكربت التالي لا يحتاج أي مكتبة خارجية، ويعمل على Windows أو Linux بلغة Python 3، وفيه ثلاثة أوامر:

```python
"""cve_watch.py: a small CVE watcher (Python 3 standard library only).

1) match:  ask OSV which known vulnerabilities affect the Debian packages in inventory.json
2) watch:  ask NVD for CVEs published or modified in the last N hours that mention the
           products we run, and print only the ones not seen before (state in seen.json)
3) feeds:  read the vendors' own advisory feeds (RSS/Atom) in feeds.json and print the
           entries of the last N hours

usage:  python cve_watch.py match
        python cve_watch.py watch --hours 48
        python cve_watch.py feeds --hours 48
"""
import json, sys, re, time, email.utils, xml.etree.ElementTree as ET, urllib.request, urllib.parse, datetime, pathlib

HERE = pathlib.Path(__file__).parent
UA = {"User-Agent": "cve-watch-lab/1.0", "Content-Type": "application/json"}

def http_json(url, body=None):
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(url, data=data, headers=UA)
    for attempt in range(3):          # NVD is slow at times and allows 5 requests per 30 s without a key
        try:
            with urllib.request.urlopen(req, timeout=120) as r:
                return json.load(r)
        except (TimeoutError, OSError):
            time.sleep(10)
    raise SystemExit(f"no answer from {url}")

def match():
    inv = json.loads((HERE / "inventory.json").read_text(encoding="utf-8"))
    queries, labels = [], []
    for server, pkgs in inv.items():
        for name, version in pkgs.items():
            queries.append({"package": {"name": name, "ecosystem": "Debian:12"}, "version": version})
            labels.append((server, name, version))
    res = http_json("https://api.osv.dev/v1/querybatch", {"queries": queries})["results"]
    for (server, name, version), r in zip(labels, res):
        ids = sorted({v["id"] for v in r.get("vulns", [])})
        week = [i for i in ids if any(i.endswith(x) for x in ("2026-91403", "2026-109618", "2026-109619", "2026-111497"))]
        print(f"{server:12} {name:16} {version:24} known={len(ids):3}  this-week={','.join(week) or '-'}")

def watch(hours):
    products = json.loads((HERE / "products.json").read_text(encoding="utf-8"))
    seen_file = HERE / "seen.json"
    seen = set(json.loads(seen_file.read_text())) if seen_file.exists() else set()
    end = datetime.datetime.now(datetime.timezone.utc)
    start = end - datetime.timedelta(hours=hours)
    fmt = "%Y-%m-%dT%H:%M:%S.000Z"
    new = 0
    for p in products:                # {"keyword": text sent to NVD, "match": regex the description must match}
        q = urllib.parse.urlencode({"keywordSearch": p["keyword"], "lastModStartDate": start.strftime(fmt),
                                    "lastModEndDate": end.strftime(fmt)})
        data = http_json("https://services.nvd.nist.gov/rest/json/cves/2.0?" + q)
        time.sleep(6)
        for item in data.get("vulnerabilities", []):
            cve = item["cve"]
            desc = " ".join(cve["descriptions"][0]["value"].split())
            if cve["id"] in seen or not re.search(p["match"], desc):
                continue
            seen.add(cve["id"])
            new += 1
            metrics = cve.get("metrics", {})
            score = "-"
            for key in ("cvssMetricV40", "cvssMetricV31"):
                if metrics.get(key):
                    score = metrics[key][0]["cvssData"]["baseScore"]
                    break
            print(f"NEW [{p['keyword']}] {cve['id']} score={score} status={cve['vulnStatus']} published={cve['published'][:16]} {desc[:70]}")
    seen_file.write_text(json.dumps(sorted(seen)))
    print(f"{new} new CVE(s) since the last run, window {start:%Y-%m-%d %H:%M} to {end:%H:%M} UTC")

def feeds(hours):
    since = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(hours=hours)
    for url in json.loads((HERE / "feeds.json").read_text(encoding="utf-8")):
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 cve-watch-lab/1.0"})
        root = ET.fromstring(urllib.request.urlopen(req, timeout=60).read())
        atom = "{http://www.w3.org/2005/Atom}"
        for e in root.iter("item"):                      # RSS 2.0
            when = email.utils.parsedate_to_datetime(e.findtext("pubDate"))
            if when >= since:
                print(f"{when:%Y-%m-%d %H:%M} {url.split('/')[2]:16} {e.findtext('title')}")
        for e in root.iter(atom + "entry"):              # Atom
            when = datetime.datetime.fromisoformat(e.findtext(atom + "updated"))
            if when >= since:
                print(f"{when:%Y-%m-%d %H:%M} {url.split('/')[2]:16} {e.findtext(atom + 'title')}")

if __name__ == "__main__":
    if sys.argv[1:2] == ["match"]:
        match()
    elif sys.argv[1:2] == ["watch"]:
        watch(int(sys.argv[3]) if len(sys.argv) > 3 else 24)
    elif sys.argv[1:2] == ["feeds"]:
        feeds(int(sys.argv[3]) if len(sys.argv) > 3 else 24)
    else:
        print(__doc__)
```

وثلاثة ملفات صغيرة بجانبه، الأول جرد الحزم Inventory بأسماء الحزم المصدرية Source Packages في Debian ونسخها كما جمعناها من السيرفرات، ولاحظ أن الحزمة libbrotli1 بالنسخة `1.0.9-2+b6` تتبع الحزمة المصدرية brotli بالنسخة `1.0.9-2`، وأن Node.js من NodeSource ليس له مكان هنا لأنه ليس حزمة Debian:

```json
{
  "srv-java":    {"openjdk-17": "17.0.20.1+1-1~deb12u1", "ca-certificates": "20250419~deb12u1", "brotli": "1.0.9-2"},
  "srv-php":     {"php8.2": "8.2.34-1~deb12u1", "ca-certificates": "20250419~deb12u1", "brotli": "1.0.9-2"},
  "srv-node":    {"ca-certificates": "20250419~deb12u1", "brotli": "1.0.9-2"},
  "srv-patched": {"openjdk-17": "17.0.20.1+1-1~deb12u1", "php8.2": "8.2.35-1~deb12u1", "ca-certificates": "20250419~deb12u2", "brotli": "1.0.9-2"}
}
```

والثاني المنتجات التي نراقبها في NVD مع تعبير نمطي Regular Expression يجب أن يطابق الوصف، والثالث خلاصات RSS وAtom الرسمية للموردين:

```json
[
  {"keyword": "PHP",      "match": "^PHP "},
  {"keyword": "Java SE",  "match": "Oracle Java SE"},
  {"keyword": "Node.js",  "match": "Node[.]js"},
  {"keyword": "Brotli",   "match": "Brotli"}
]
```

```json
[
  "https://nodejs.org/en/feed/vulnerability.xml",
  "https://www.php.net/feed.atom",
  "https://www.oracle.com/ocom/groups/public/@otn/documents/webcontent/rss-otn-sec.xml"
]
```

### المطابقة Matching مع OSV: أي سيرفر متأثر؟

```bash
python cve_watch.py match
```

والمخرج في الساعة 18:11 UTC من يوم 22 أكتوبر كان كما يلي:

```text
srv-java     openjdk-17       17.0.20.1+1-1~deb12u1    known= 40  this-week=DEBIAN-CVE-2026-91403
srv-java     ca-certificates  20250419~deb12u1         known=  0  this-week=-
srv-java     brotli           1.0.9-2                  known=  1  this-week=DEBIAN-CVE-2026-111497
srv-php      php8.2           8.2.34-1~deb12u1         known=  2  this-week=DEBIAN-CVE-2026-109618,DEBIAN-CVE-2026-109619
srv-php      ca-certificates  20250419~deb12u1         known=  0  this-week=-
srv-php      brotli           1.0.9-2                  known=  1  this-week=DEBIAN-CVE-2026-111497
srv-node     ca-certificates  20250419~deb12u1         known=  0  this-week=-
srv-node     brotli           1.0.9-2                  known=  1  this-week=DEBIAN-CVE-2026-111497
srv-patched  openjdk-17       17.0.20.1+1-1~deb12u1    known= 40  this-week=DEBIAN-CVE-2026-91403
srv-patched  php8.2           8.2.35-1~deb12u1         known=  0  this-week=-
srv-patched  ca-certificates  20250419~deb12u2         known=  0  this-week=-
srv-patched  brotli           1.0.9-2                  known=  1  this-week=DEBIAN-CVE-2026-111497
```

والنتيجة مطابقة لنتيجة Trivy تماماً، وهذا منطقي لأن الاثنين يقرآن بيانات Debian نفسها، فتقول OSV إن PHP على srv-php متأثر وعلى srv-patched مصلح، وإن openjdk-17 متأثر على السيرفرين، ولا تقول شيئاً عن ca-certificates لأن إصلاح الشهادات نشر في Debian بنشرة DLA دون رقم CVE.

### المراقبة Monitoring: ما الجديد في المنتجات التي نستخدمها؟

```bash
python cve_watch.py watch --hours 48
```

في التشغيل الأول طبع السكربت 107 تنبيهات خلال 48 ساعة، وهذه أهم الأسطر منها كما ظهرت:

```text
NEW [PHP] CVE-2026-109618 score=9.2 status=Modified published=2026-10-21T14:17 PHP uses longjmp() for out-of-memory (OOM) errors to exit the current
NEW [PHP] CVE-2026-109619 score=9.2 status=Modified published=2026-10-21T14:17 PHP has various global state that temporarily points into request memo
NEW [Java SE] CVE-2026-91403 score=9.1 status=Awaiting Analysis published=2026-10-21T22:16 Vulnerability in the Oracle Java SE, Oracle GraalVM for JDK, Oracle Gr
NEW [Node.js] CVE-2026-112951 score=5.4 status=Deferred published=2026-10-20T16:17 ApostropheCMS is an open-source Node.js content management system. Pri
NEW [Node.js] CVE-2026-111756 score=8.2 status=Awaiting Analysis published=2026-10-20T18:17 follow-redirects is a Node.js HTTP and HTTPS client wrapper that autom
NEW [Brotli] CVE-2026-111497 score=9.2 status=Awaiting Analysis published=2026-10-22T14:17 A buffer overflow vulnerability in the Brotli decoder (c/dec/decode.c)
107 new CVE(s) since the last run, window 2026-10-20 18:16 to 18:16 UTC
```

وفي التشغيل الثاني بعد دقيقة واحدة كان المخرج:

```text
0 new CVE(s) since the last run, window 2026-10-20 18:16 to 18:16 UTC
```

في المخرج أعلاه لاحظ التالي:

- **الملف seen.json يمنع التكرار:** فالتشغيل الثاني لم يطبع شيئاً، وهذا ما يجعل السكربت صالحاً للتشغيل كل ساعة من Task Scheduler في Windows أو cron في Linux، ثم ترسل مخرجه بالبريد أو إلى Webhook عندما لا يكون فارغاً.
- **البحث بالكلمة Keyword مزعج:** في المحاولة الأولى بحثنا عن PHP دون شرط على الوصف، فطبع السكربت عشرات الثغرات في إضافات WordPress وJoomla وتطبيقات مكتوبة بلغة PHP قبل أن يصل إلى ثغرتي PHP نفسها، ولذلك أضفنا الشرط `^PHP `. وحتى مع الشرط فإن كلمة Node.js أعادت أكثر من 40 ثغرة في مكتبات npm وApostropheCMS، لأن وصفها يذكر Node.js.
- **البحث بالمعرف CPE دقيق لكنه متأخر:** جربنا الاستعلام بالمعامل `virtualMatchString` على `cpe:2.3:a:php:php` فأعاد ثغرتي PHP فقط، أما `cpe:2.3:a:oracle:jdk` و`cpe:2.3:a:nodejs:node.js` و`cpe:2.3:a:google:brotli` فلم تعد شيئاً، لأن السجلات الثلاثة كانت في حالة Awaiting Analysis ولم تضف إليها NVD معرفات CPE بعد.
- **ثغرة Node.js وصلت باسم Brotli:** سجل CVE-2026-111497 نشرته Google بصفتها CNA ووصفه لا يذكر Node.js، فلو اشتركت في Node.js وحده لفاتك، وهذا ما يحدث أيضاً في أي أداة تشترك فيها بالمورد والمنتج.
- **بقية ثغرات Node.js لم تصل بعد:** نشرة Node.js تذكر عشر ثغرات، لكن السجلات مثل CVE-2026-105482 لم تكن منشورة في CVE.org ولا في NVD حتى الساعة 18:17، أي بعد عشرين ساعة من النشرة.

### لذلك راقب نشرات الموردين Vendor Advisories أيضاً

```bash
python cve_watch.py feeds --hours 48
```

```text
2026-10-21 22:00 nodejs.org       Wednesday, October 21, 2026 Security Releases
2026-10-21 00:35 www.php.net      Security advisory: Critical remote code execution vulnerabilities, update now
2026-10-21 00:24 www.php.net      PHP 8.5.12 Released!
2026-10-21 00:20 www.php.net      PHP 8.4.27 Released!
2026-10-21 00:14 www.php.net      PHP 8.3.36 Released!
2026-10-21 00:05 www.php.net      PHP 8.2.35 Released!
2026-10-20 20:32 www.php.net      Upcoming Critical PHP Security Release
2026-10-20 13:00 www.oracle.com   Oracle Critical Security Patch Update Advisory - October 2026
```

وهنا يظهر الفرق، فموقع PHP أعلن عن إصدار أمني حرج قبل صدوره بنحو ثلاث ساعات ونصف، ثم نشر الإصدارات بعد منتصف الليل، أي قبل سجلات CVE بأربع عشرة ساعة، ونشرة Node.js وصلت في خلاصتها قبل أن يصل أغلب سجلاتها إلى CVE.org. أما خلاصة Oracle فلم يظهر فيها في ذلك الوقت إلا التحديث الدوري CPU في 20 أكتوبر، دون التنبيه الخاص [Security Alert](https://www.oracle.com/security-alerts/alert-cve-2026-91403.html?ref=arabroot.io) بالثغرة CVE-2026-91403، فلا تعتمد على خلاصة واحدة لكل مورد. والشكل التالي يجمع توقيت كل مصدر وكل أداة:

![خط زمني من 20 إلى 22 أكتوبر 2026 يبين متى نشر كل مورد وسجل CVE ومتى عرفت Trivy وWazuh وسكربت التنبيه](https://arabroot.io/content/images/2026/10/vuln-lab-02-timeline.webp)

نشرات الموردين أولاً، ثم سجلات CVE، ثم قواعد الأدوات

## الإصلاح Patch ثم إعادة الفحص

الإصلاح على سيرفر Debian يعني أن تعيد المستودع الأمني إلى ملف المصادر إذا كان معطلاً، ثم تحدث الحزم، ثم تعيد تشغيل الخدمة التي تحمل الكود القديم في الذاكرة:

```bash
apt-get update
apt-get install --only-upgrade libapache2-mod-php8.2 php8.2-cli php8.2-common php8.2-opcache php8.2-readline ca-certificates
apachectl -k graceful
```

وقد يتساءل البعض: لماذا إعادة تشغيل Apache إذا كانت الحزمة قد تحدثت؟ والإجابة أن mod\_php يعمل داخل عمليات Apache نفسها، وقبل الإصلاح كانت هذه العمليات تعمل منذ بدء السيرفر كما يبين الأمر التالي على srv-php:

```bash
php -v | head -1
ps -o pid,lstart,cmd -C apache2 | head -3
```

```text
PHP 8.2.34 (cli) (built: Oct  4 2026 14:03:09) (NTS)
    PID                  STARTED CMD
    391 Thu Oct 22 14:52:26 2026 /usr/sbin/apache2 -k start
    413 Thu Oct 22 14:52:27 2026 /usr/sbin/apache2 -k start
```

فإذا حدثت الحزمة ولم تعد تشغيل Apache بقيت العمليات القديمة تنفذ PHP 8.2.34 المتأثر، والفاحص يقرأ قاعدة بيانات الحزم فيظن أن السيرفر مصلح. لذلك اجعل إعادة التشغيل جزءاً من خطوة الإصلاح نفسها، وتحقق من وقت بدء العمليات بعد الإصلاح.

وفي المختبر كان السيرفر srv-patched هو نتيجة هذا الإصلاح، فهو الملف Dockerfile نفسه مع المستودعات الحية وأمر `apt-get -y upgrade`، ولم نكرر التحديث داخل السيرفرات القديمة نفسها لأن Docker توقف قبل ذلك. ونتيجة Trivy عليه بعد الإصلاح ظهرت في قسم Trivy أعلاه، وهي كما يلي:

| الثغرة                        | قبل الإصلاح                   | بعد الإصلاح               | هل النتيجة صحيحة؟                                |
| ----------------------------- | ----------------------------- | ------------------------- | ------------------------------------------------ |
| CVE-2026-109618 و109619 (PHP) | 5 حزم CRITICAL بحالة fixed    | لا شيء                    | نعم، أغلقت النتيجة                               |
| CVE-2026-91403 (Java)         | openjdk-17 بحالة affected     | openjdk-17 بحالة affected | لا، إنذار خاطئ False Positive بعد تحديث الشهادات |
| DLA-4845-1 (الشهادات)         | ca-certificates قابلة للإصلاح | لا شيء                    | نعم، وهي الإصلاح الحقيقي لثغرة Java              |
| CVE-2026-111497 (Node.js)     | لا شيء على nodejs             | لا شيء على nodejs         | لا، ثغرة فاتت الأداة False Negative قبل الإصلاح  |
| CVE-2026-111497 (libbrotli1)  | affected                      | affected                  | صحيحة للمكتبة، ولا إصلاح لها في Debian 12 بعد    |

### لماذا بقيت ثغرة Java مفتوحة رغم الإصلاح؟ إنذار خاطئ False Positive

تقول [نشرة OpenJDK في 21 أكتوبر](https://openjdk.org/groups/vulnerability/advisories/2026-10-21?ref=arabroot.io) إن CVE-2026-91403 تتعلق بثماني شهادات وسيطة Intermediate CA مخترقة، وإن Java المثبت من حزم التوزيعة في Debian وUbuntu وRed Hat يستخدم شهادات النظام، فيكفي تحديث شهادات النظام. وقد أصدرت Debian هذا الإصلاح في [DLA-4845-1](https://lists.debian.org/debian-lts-announce/2026/10/msg00039.html?ref=arabroot.io) يوم 20 أكتوبر، فحذفت الشهادتين «emSign ECC Root CA - G3» و«Comodo AAA Services root». ولنتحقق بأنفسنا، نقارن مخزن الشهادات Truststore الذي يستخدمه Java على السيرفرين:

```bash
keytool -list -cacerts -storepass changeit 2>/dev/null | grep -i -E "emsign|aaa"
keytool -list -cacerts -storepass changeit | grep -c trustedCertEntry
```

والمخرج على srv-java قبل الإصلاح:

```text
debian:comodo_aaa_services_root.pem, Oct 22, 2026, trustedCertEntry,
debian:emsign_ecc_root_ca_-_c3.pem, Oct 22, 2026, trustedCertEntry,
debian:emsign_ecc_root_ca_-_g3.pem, Oct 22, 2026, trustedCertEntry,
debian:emsign_root_ca_-_c1.pem, Oct 22, 2026, trustedCertEntry,
debian:emsign_root_ca_-_g1.pem, Oct 22, 2026, trustedCertEntry,
150
```

والمخرج على srv-patched بعد تحديث ca-certificates:

```text
debian:emsign_ecc_root_ca_-_c3.pem, Oct 22, 2026, trustedCertEntry,
debian:emsign_root_ca_-_c1.pem, Oct 22, 2026, trustedCertEntry,
debian:emsign_root_ca_-_g1.pem, Oct 22, 2026, trustedCertEntry,
148
```

فالشهادتان اختفتا من مخزن Java نفسه، أي أن الخطر الذي تصفه النشرة زال، ومع ذلك يبقى Trivy وOSV يعرضان الثغرة على openjdk-17 لأن متتبع Debian يربط الرقم بحزمة openjdk-17 وينتظر إصدارها الجديد، ويعرض إصلاح الشهادات تحت رقم DLA منفصل. والحل الأنسب هنا أن تسجل استثناء Exception لهذه النتيجة مع السبب ورابط النشرة وتاريخ انتهاء، كما شرحنا في [فحص ال Docker Images](https://arabroot.io/articles/%D9%81%D8%AD%D8%B5-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-%D9%83%D9%83%D9%88%D8%AF-%D9%85%D8%B9-trivy/)، ثم تزيله عندما تصل حزمة openjdk-17 الجديدة، ولا تقم بإخفاء الرقم نهائياً، والسبب أن النشرة نفسها قد تتغير.

## مقارنة النتائج Results: من رأى ماذا؟

وبعد التجربة نجمع ما رأته كل طريقة في جدول واحد، والحكم فيه على الثغرات الأربع فقط:

| السؤال                  | Trivy rootfs                                      | Wazuh                          | سكربت OSV وNVD والخلاصات                     |
| ----------------------- | ------------------------------------------------- | ------------------------------ | -------------------------------------------- |
| PHP على srv-php         | نعم، مع النسخة المصلحة                            | لا، حتى 16:17                  | نعم في OSV، والخلاصة قبل CVE بأربع عشرة ساعة |
| Java على srv-java       | نعم، بلا إصلاح                                    | لا                             | نعم في OSV وNVD                              |
| Java بعد تحديث الشهادات | إنذار خاطئ                                        | لم نقسه                        | إنذار خاطئ في OSV                            |
| Node.js من NodeSource   | فاتته                                             | لا                             | عبر Brotli في NVD ونشرة nodejs.org فقط       |
| Java من Temurin في مجلد | فاته                                              | لم نقسه                        | لا يعرف ما ثبت خارج الحزم                    |
| الموارد                 | ثوان، مع تنزيل قاعدة Java بحجم 941 MiB في أول فحص | 4.4 GiB RAM وأكثر من 16 GB قرص | لا شيء تقريباً                               |

## مشكلات شائعة Common Problems وحلولها

- **رسالة `context deadline exceeded` من Trivy:** السبب تنزيل قاعدة Java في أول تشغيل، فارفع المهلة بالخيار `--timeout 15m` أو نزل القواعد مسبقاً.
- **رسالة `template file must have .tpl extension`:** غير امتداد ملف القالب إلى `.tpl`.
- **رسالة `Depends: perl-base (= …) but … is to be installed` عند بناء سيرفر قديم:** ال Base Image أحدث من لحظة الأرشيف، فاختر Image أقدم من تاريخ snapshot.
- **لوحة Wazuh لا تعرض ثغرة نشرت اليوم:** تحقق من تاريخ أحدث ثغرة في الفهرس ومن سطر `Initiating update feed process` في السجل، فقد تكون القاعدة لم تتحدث بعد، وافحص بأداة أخرى في يوم النشرة.
- **Docker يتوقف عن الاستجابة أثناء المختبر:** راقب المساحة الحرة على قرص السيرفر المضيف قبل تشغيل Wazuh وOpenCVE معاً، فهما يحتاجان معاً أكثر من 30 GB.
- **NVD ترد ببطء أو بخطأ:** بدون مفتاح API يسمح بخمسة طلبات كل ثلاثين ثانية، والسكربت ينتظر ست ثوان بين الطلبات ويعيد المحاولة ثلاث مرات.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما ظهر في المختبر:

- الفحص بدون وكيل مع Trivy أعطى أدق نتيجة لثغرة PHP خلال ثوان، مع النسخة التي تصلحها، وهو الخيار الأنسب لمن يدير عدداً قليلاً من السيرفرات.
- Wazuh يعطيك صورة مستمرة لكل السيرفرات ويغلق النتيجة وحده بعد التحديث، لكنه احتاج 4.4 GiB من الذاكرة وأكثر من 16 GB من القرص، ولم يعرف ثغرات أكتوبر خلال ثلاث ساعات ونصف من التشغيل، فلا تعتمد عليه وحده في يوم النشرة.
- كل أداة تعتمد على قاعدة بيانات الحزم يفوتها ما ثبت خارجها، مثل Node.js من NodeSource وJava من ملف مضغوط، ولذلك فجردك لما ثبتته بيدك جزء أساسي من إدارة الثغرات.
- الإصلاح قد يكون في حزمة أخرى، كما في ثغرة Java التي أصلحتها حزمة الشهادات، فتبقى النتيجة مفتوحة وهي إنذار خاطئ، وتحتاج استثناء موثقاً بتاريخ انتهاء.
- التنبيه بالجديد لا يحتاج أداة كبيرة، فسكربت صغير يسأل OSV وNVD ونشرات الموردين يكفي لعدد قليل من المنتجات، ونشرات الموردين تسبق سجلات CVE بساعات.
- لا يكتمل الإصلاح قبل إعادة تشغيل الخدمة التي تحمل الكود القديم، ثم إعادة الفحص.

[![](https://arabroot.io/content/images/2026/10/vuln-management-cover-cover.webp)دليلإدارة الثغرات Vulnerability Management: هل سيرفرك متأثر؟كيف تعرف خلال ساعات أن ثغرة جديدة تمس سيرفراتك، ولماذا يخدعك رقم الإصدار، وكيف ترتب الإصلاح وتتأكد أنه تم فعلاً.](https://arabroot.io/articles/%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AB%D8%BA%D8%B1%D8%A7%D8%AA-vulnerability-management-%D9%84%D8%B3%D9%8A%D8%B1%D9%81%D8%B1%D8%A7%D8%AA%D9%83/)[![](https://arabroot.io/content/images/2026/10/vuln-tools-cover-cover.webp)دليلأدوات إدارة الثغرات Vulnerability Management مفتوحة المصدرأي أداة مفتوحة المصدر تجرد سيرفراتك وتطابقها مع الثغرات وتنبهك بالجديد، وماذا رأى Trivy وGrype فعلاً في ثغرات أكتوبر.](https://arabroot.io/articles/%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AB%D8%BA%D8%B1%D8%A7%D8%AA-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/)[![](https://arabroot.io/content/images/2026/10/container-scanning-cover-cover.webp)دليلفحص ال Docker Images وملفات البنية ككود IaCماذا يرى فاحص الثغرات داخل ال Docker Image وما الذي يفوته، وكيف تختار Base Image أصغر وتجعل الفحص بوابة في خط CI.](https://arabroot.io/articles/%D9%81%D8%AD%D8%B5-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-%D9%83%D9%83%D9%88%D8%AF-%D9%85%D8%B9-trivy/)[![](https://arabroot.io/content/images/2026/10/sca-cover-cover.webp)دليلSCA: كيف تجد الثغرات المعروفة في مكتبات مشروعككيف تجد أداة فحص الاعتماديات SCA المكتبات المصابة في مشروعك، ولماذا تختلف نتائج الأدوات، وكيف تبقي مكتباتك محدثة.](https://arabroot.io/articles/%D9%81%D8%AD%D8%B5-%D8%A7%D9%84%D8%A7%D8%B9%D8%AA%D9%85%D8%A7%D8%AF%D9%8A%D8%A7%D8%AA-sca-%D9%85%D8%B9-osv-scanner-%D9%88-dependency-track/)[![](https://arabroot.io/content/images/2026/10/secure-vps-cover-cover-2.webp)دليلتأمين سيرفر VPS على Ubuntu من أول دخولقبل أن تثبت أي تطبيق على سيرفرك الجديد، هذه الخطوات تغلق أبرز الثغرات وأولها الدخول بمفتاح SSH بدل كلمة المرور.](https://arabroot.io/articles/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-vps-%D9%85%D9%86-%D8%A3%D9%88%D9%84-%D8%AF%D8%AE%D9%88%D9%84/)

## تدريب عملي Practice

- اكتب ملف inventory.json لسيرفراتك من مخرج `dpkg-query -W -f='${source:Package} ${source:Version}\n'`، ثم شغل `python cve_watch.py match` وقارن النتيجة بما تعرفه عن كل سيرفر.
- ابحث في سيرفراتك عن البرامج المثبتة خارج مدير الحزم، مثل المجلدات في `/opt` والملفات التنفيذية في `/usr/local/bin`، واكتبها في جرد منفصل مع مصدر نشرات كل منها.

## سجل التحديثات

- أكتوبر 2026: كتابة المقال وتنفيذ المختبر يوم 22 أكتوبر 2026 على Debian 12 مع Trivy 0.76.1 وWazuh 4.14.9 وسكربت Python للتنبيه، وقد توقف المختبر قبل أن تظهر ثغرات أكتوبر في Wazuh لأن قرص السيرفر المضيف امتلأ، فلم نقس وقت ظهورها فيه.