
مدقق حالة الترقيع غير الإتلافي لـ SolarWinds ARM CVE-2026-28326 الذي يستكشف مستمع gRPC على TCP 55555 للإبلاغ عن VULNERABLE أو PATCHED أو INCONCLUSIVE.
فحص حالة الترقيع دون مصادقة للثغرة CVE-2026-28326، وهي ثغرة تنفيذ تعليمات برمجية عن بُعد دون مصادقة
في SolarWinds Access Rights Manager (ARM)، التي تم إصلاحها في
ARM 2026.2.1
بتاريخ 2026-09-17. CWE-798، CVSS 8.8 مرتفع
(CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H). تم الإبلاغ عنها إلى SolarWinds بواسطة Kai Huang من Armadin.
يفترض AV:A في هذا المتجه أن TCP 55555 يمكن الوصول إليه فقط من شبكة مجاورة، وهو الوضع
الذي صُممت له أدوات جدار الحماية الخاصة بـ ARM نفسها؛ وحيثما تم فتح المنفذ على نطاق أوسع، فإن
الدرجة تقلل من تقدير مدى التعرض لتلك البيئة.
يُصادق مستمع gRPC remoting الخاص بـ ARM على المنفذ TCP 55555 مع الأقران باستخدام شهادة عميل. كما تقبل
الإصدارات غير المرقّعة بيانات اعتماد احتياطية مشتقة من مفتاح مُضمَّن ثابت متطابق في كل
عمليات التثبيت، مما يتجاوز تلك المصادقة ويكشف مسار إلغاء تسلسل غير آمن. والنتيجة هي
تنفيذ تعليمات برمجية بحساب خدمة ARM، الذي يعمل كـ LocalSystem افتراضيًا.
يُبلّغ النص البرمجي عمّا إذا كان الإصلاح موجودًا على كل هدف. فهو يقرأ رموز الحالة التي يعيدها
معترض المصادقة ولا يستخدم المفتاح، ولا يُصادق، ولا يرسل بيانات إلى
مسار إلغاء التسلسل. النتيجة غير VULNERABLE لا تعني بحد ذاتها أن الهدف
مرقّع؛ راجع تفسير النتائج غير القابلة للاستغلال.
# single target (port defaults to 55555)
./cve_2026_28326_check.py arm.example.com
# explicit port
./cve_2026_28326_check.py arm.example.com:55555
# several targets
./cve_2026_28326_check.py arm-a.example.com arm-b.example.com
# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_28326_check.py -f targets.txt --brief
# machine-readable output for pipelines
./cve_2026_28326_check.py -f targets.txt --json > results.json
Python 3.8+، المكتبة القياسية فقط — لا حزم خارجية.
شغّل الأداة ضد خادم ARM وليس ضد مجمّع (collector). كلا الدورين يستمع على 55555، لكن
المجمّع يرفض الطلبات الخارجية بالطريقة نفسها قبل الإصلاح وبعده، لذا تُبلّغ الأداة عنه
بـ INCONCLUSIVE.
خادم قابل للاستغلال (الإخراج الافتراضي؛ تُعرض العلامة [!] وVULNERABLE باللون الأحمر على TTY):
$ ./cve_2026_28326_check.py arm.example.com
[!] arm.example.com:55555: VULNERABLE [unavailable-on-server-role]
Remoting/Send returned 14 UNAVAILABLE on a listener answering as the server role, the pre-patch rejection path (<= 2026.2.0.42)
خادم مرقّع. يعيد الإصدار المُصلَّح grpc-message، الذي تطبعه الأداة إلى جانب
الحكم:
$ ./cve_2026_28326_check.py arm-dr.example.com
[+] arm-dr.example.com:55555: PATCHED [unauthenticated-gate]
Remoting/Send returned 16 UNAUTHENTICATED, the localhost and per-process-token gate introduced by the fix (>= 2026.2.1.7)
grpc-message: Client certificate not found. Please register your application first.
مستمع لا يمكن تصنيفه. المجمّع، أو خادم يرفض الطلبات الخارجية، يجيب على كلا الفحصين بالطريقة نفسها على أي من الإصدارين:
$ ./cve_2026_28326_check.py collector.example.com
[?] collector.example.com:55555: INCONCLUSIVE [external-requests-refused]
the listener refuses external requests, which a collector role and a restricted server both do identically on either build; scan the ARM server itself
أهداف متعددة مع --brief:
$ ./cve_2026_28326_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE arm.example.com:55555 unavailable-on-server-role
PATCHED arm-dr.example.com:55555 unauthenticated-gate
INCONCLUSIVE collector.example.com:55555 external-requests-refused
UNAFFECTED db.example.com:55555 not-arm
ERROR offline.example.com:55555 unreachable
exit: 1
إخراج JSON مع --json. تُضمَّن نتائج كلا الفحصين حتى يمكن التحقق من الحكم مقابل
الاستجابات الخام:
$ ./cve_2026_28326_check.py arm.example.com --json
[
{
"target": "arm.example.com:55555",
"verdict": "VULNERABLE",
"reason": "unavailable-on-server-role",
"detail": "Remoting/Send returned 14 UNAVAILABLE on a listener answering as the server role, the pre-patch rejection path (<= 2026.2.0.42)",
"grpc_message": "",
"probes": [
{
"path": "/SolarWinds_ARM_GRPC_ConnectionAuthrization.ConnectionAuthorization/GetCertificate",
"status": 0,
"message": "",
"alpn": "h2",
"note": ""
},
{
"path": "/SolarWinds_ARM_Remoting.Remoting/Send",
"status": 14,
"message": "",
"alpn": "h2",
"note": ""
}
]
}
]
الفحص مخصص للاستخدام ضد أنظمة الإنتاج:
Remoting/Send. يرسل كلا الفحصين رسالة gRPC بجسم بطول صفر،
ويرفض المعترض الاستدعاء قبل إرسال أي جسم رسالة.GetCertificate رمز تسجيل فارغًا، يرفضه الخادم قبل أن يبحث عن أي رموز تسجيل معلّقة
أو يزيلها، لذا لا يستهلك الفحص رمز تسجيل لعميل في
منتصف عملية التسجيل.| # | الاستدعاء | الغرض |
|---|---|---|
| 1 | ConnectionAuthorization/GetCertificate | يؤكد أن نقطة النهاية هي ARM وأنها في دور الخادم |
| 2 | Remoting/Send | فحص حالة الترقيع |
على خادم غير مرقّع، يُسجَّل طلب مجهول مرفوض عند مستوى التصحيح
(Unable to validate remote request token) وسطر معلوماتي يذكر اسم القرين. أما الإصدارات
المرقّعة فتسجّل رفضًا يذكر القرين وتلميح التسجيل المقتبس في grpc-message أعلاه.
لا يسجّل أي من الإصدارين جلسة.
للبحث عن الاستغلال بدلًا من التعرض، راجع سجلات NetworkConnectionEntry الخاصة بـ ARM. إن
مدخلًا بـ IsAuthenticated=false وClientCertThumbPrint مُعبَّأ يتوافق مع
تجاوز المصادقة ولا يحدث في حركة المرور الطبيعية.
غيّر الإصلاح الحالة التي يعيدها معترض المصادقة إلى قرين لا يقدّم أي شهادة
عميل. قبل الترقيع، كان كل مسار رفض في فحص شهادة العميل يضبط Unavailable.
وبعد الترقيع، يضبط فحص localhost-plus-token الحالة Unauthenticated مع رسالة مميزة،
ولا شيء آخر في ذلك المسار يعيد 16. يميّز استدعاءان مجهولان بين الإصدارين:
يُقرأ الحكم من Remoting/Send لأن معالجات duplex-streaming وunary فقط
هي التي تنشر حالة المعترض؛ أما معالجات server- وclient-streaming فتستبدلها على كلا
الإصدارين.
يراقب الفحص سلوك مسار التعليمات البرمجية المرقّع ولا يعتمد على سلسلة إصدار. ولا يُبلّغ عن الإصدار الذي يعمل عليه خادم مرقّع.
يعيد مستمع ARM ضبط اتصالات TLS التي لا تتفاوض على ALPN h2، لذا تضبطه الأداة
صراحةً. الماسح الذي يتصل عبر TLS دون ALPN لا يتلقى أي بيانات من المنفذ.
تستند كل من VULNERABLE وPATCHED إلى استجابة خادم مرصودة. أما INCONCLUSIVE و
ERROR فيعنيان أن الفحصين لم يصنّفا الهدف، لذا فإن حالة ترقيعه غير معروفة. لهذا السبب
يُبلَّغ عن INCONCLUSIVE بشكل منفصل عن PATCHED.
يحمل كل حكم وسم reason قصيرًا. يطبعه --brief كعمود ثالث ويحمله --json
كحقل reason.
بالنسبة لجميع أسباب INCONCLUSIVE الثلاثة، حالة الترقيع غير معروفة. ويشير وسم السبب إلى ما يجب
معالجته قبل تشغيل الفحص مرة أخرى.
| الرمز | المعنى |
|---|
يغطي رمز الخروج 0 الحالات PATCHED وUNAFFECTED وجميع أسباب INCONCLUSIVE الثلاثة. للتمييز
بين هدف مرقّع وهدف تعذّر تصنيفه، اقرأ الحكم (العمود الأول من
--brief أو حقل verdict في --json) بدلًا من رمز الخروج.
INCONCLUSIVE. اختُبرت هذه الحالة مقابل محاكاة تعيد إنتاج الاستجابة، وليس مقابل
مجمّع منشور.grpc-status كحقل HPACK حرفي ولا تنفّذ فك ترميز
Huffman أو الجدول الديناميكي. يرسل نواة gRPC بلغة C، التي يستخدمها ARM، هذه
التذييلات غير مضغوطة. إذا رمّزها إصدار مستقبلي بترميز Huffman، فستُبلّغ الأداة عن INCONCLUSIVE مع
partial-response.PATCHED على هذه الثغرة فقط ولا يشير إلى حالة ثغرات ARM الأخرى.VULNERABLE إلى أن الإصلاح غير موجود على المسار الذي
وصل إليه الفحص. لا تنفّذ الأداة تعليمات برمجية على الهدف ولا تكتشف اختراقًا سابقًا؛
راجع مؤشر NetworkConnectionEntry أعلاه.قم بالترقية إلى ARM 2026.2.1.7 أو أحدث، وفقًا لملاحظات إصدار ARM 2026.2.1. الإصدارات 2026.2.0.42 وما قبلها متأثرة.
ملاحظات إضافية:
يُوزَّع هذا الكود بموجب ترخيص MIT.
استخدام هذه الأداة لمهاجمة أهداف دون موافقة متبادلة مسبقة غير قانوني. تقع على عاتق المستخدم النهائي مسؤولية الالتزام بجميع القوانين المحلية والولائية والفيدرالية المعمول بها. لا يتحمل المطوّرون أي مسؤولية ولا يُسألون عن أي إساءة استخدام أو ضرر ناتج عن هذا البرنامج.
| العلم | الوصف |
|---|
TARGET | هدف واحد أو أكثر بصيغة HOST[:PORT]؛ المنفذ الافتراضي هو 55555 |
-f, --targets-file FILE | قراءة الأهداف من ملف (واحد في كل سطر؛ تعليقات #) |
-p, --port PORT | المنفذ الافتراضي عندما يحذفه الهدف (الافتراضي: 55555) |
--timeout SECS | المهلة لكل فحص (الافتراضي: 12) |
--workers N | الأهداف المتزامنة (الافتراضي: 16)؛ يبقى الإخراج بترتيب الإدخال |
-b, --brief | سطر واحد مُحاذى لكل هدف، لفحص العديد من المضيفين |
--json | إخراج JSON مُهيكل، بما في ذلك كلا الفحصين لكل هدف |
--no-color | تعطيل الإخراج الملوّن (يحترم أيضًا NO_COLOR وغير TTY) |
Remoting/Send | GetCertificate | الإصدار | الحكم |
|---|
14 UNAVAILABLE | 0 OK | <= 2026.2.0.42 | VULNERABLE |
16 UNAUTHENTICATED | أي قيمة | >= 2026.2.1.7 | PATCHED |
14 UNAVAILABLE | 14 UNAVAILABLE | أي منهما | INCONCLUSIVE |
| الحكم | وسم السبب | المعنى |
|---|
VULNERABLE | unavailable-on-server-role | أعاد Remoting/Send القيمة 14 على مستمع يجيب على GetCertificate بدور الخادم. الإصلاح غير موجود. |
PATCHED | unauthenticated-gate | أعاد Remoting/Send القيمة 16، وهي الحالة التي أدخلها الإصلاح. ينطبق على هذه الثغرة فقط. |
INCONCLUSIVE | external-requests-refused | أعاد كلا الفحصين القيمة 14. يتصرف دور المجمّع وخادم مقيّد بالطريقة نفسها هنا على كلا الإصدارين. افحص خادم ARM نفسه. |
INCONCLUSIVE | partial-response | أعاد فحص واحد فقط حالة، لذا لا يمكن مقارنة الزوج. أعد المحاولة. |
INCONCLUSIVE | unrecognized-status | أجابت نقطة النهاية بمزيج حالات خارج المجموعة المعروفة. |
UNAFFECTED | not-arm | لم تُجب أي خدمات ARM gRPC هنا. |
ERROR | unreachable | فشل اتصال TCP أو مصافحة TLS أو تبادل HTTP/2. |
0 | لم يكن أي هدف VULNERABLE |
1 | هدف واحد على الأقل VULNERABLE |
2 | خطأ في الاستخدام (وسائط خاطئة، أو هدف غير صالح، أو ملف أهداف غير قابل للقراءة) |
130 | مُقاطَع (Ctrl-C) |