
اكتشاف تجاوز مصادقة SAML في Citrix NetScaler CVE-2026-19490 بأمان
فحص آمن وغير مُصادَق للثغرة CVE-2026-19490، وهي تجاوز المصادقة قبل المصادقة في مسار مزوّد خدمة SAML في Citrix NetScaler ADC / NetScaler Gateway
(CTX696939،
نُشر في 2026-08-19). CWE-288، CVSS v4.0 9.3
(AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:L). أبلغ عنه Samarth Vashisht من
فريق اختبار الاختراق في JPMorgan Chase.
يقوم الجهاز بفك ترميز base64 للمعامل RelayState في استجابة SAML، وعندما يبدأ النص العادي بـ ctx=،
يمرّر الباقي إلى مُفكِّك تسلسل سياق nFactor. على إصدار غير مُرقَّع، يؤدي فشل فك التسلسل إلى نشر
طول RelayState المُفكَّك كرمز التصرف الداخلي للطلب بدلاً من خطأ،
لذا يختار المهاجم غير المُصادَق الفرع الداخلي الذي يتخذه الجهاز بعد ذلك
بمجرد اختياره لطول RelayState. بعض الفروع تُنشئ جلسة Gateway حقيقية؛
وأخرى تُعطّل محرك الحزم وتُعيد تشغيل الجهاز. هذا النص البرمجي لا يفعل أياً من ذلك
— فهو يرسل الطول الواحد المُتحقَّق منه الذي لا يُنشئ جلسة ويترك محرك الحزم وشأنه، ويجيب
على سؤال واحد لكل هدف: هل هذا الجهاز مُعرَّض للثغرة؟ أي نتيجة غير
VULNERABLE هي ليست بحد ذاتها شهادة سلامة.
# single target
./cve_2026_19490_check.py https://gateway.example.com
# a specific Gateway or AAA virtual server
./cve_2026_19490_check.py https://gateway.example.com:9443
# several targets; the scheme defaults to https://
./cve_2026_19490_check.py gw-a.example.com gw-b.example.com:9443
# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_19490_check.py -f targets.txt --brief
# machine-readable output for pipelines
./cve_2026_19490_check.py -f targets.txt --json > results.json
Python 3.8+، المكتبة القياسية فقط — لا حزم خارجية.
وجّه الأداة إلى خادم Gateway أو AAA الافتراضي، وليس إلى واجهة الإدارة. التعرّض
يكون لكل خادم افتراضي، لذا يحتاج الجهاز الذي يضم عدة VIPs إلى اختبار كل واحد منها. يحمل الفحص
غلافاً أمنياً صارماً — طول RelayState واحد مُتحقَّق منه، دون مسح — وهو ما
يوضحه هل من الآمن تشغيله؟.
جهاز مُعرَّض للثغرة (الإخراج الافتراضي المكوّن من سطرين). تظهر العلامة [!] وVULNERABLE
باللون الأحمر على TTY:
$ ./cve_2026_19490_check.py https://gateway.example.com:9443
[!] https://gateway.example.com:9443: VULNERABLE [internal-error-43524]
HTTP 500 / 43524: the decoded RelayState length was propagated as the internal error, so the CTX696939 fix is absent
جهاز مُرقَّع:
$ ./cve_2026_19490_check.py https://vpn.example.com
[+] https://vpn.example.com: PATCHED [fixed-error-returned]
HTTP 200 "Malformed Assertion": the fixed error was returned on the path this probe reached, so the CTX696939 fix is present (>= 13.1-63.21 / 14.1-73.32)
تفعيل حارس الإيجابيات الكاذبة. أعاد كلٌّ من الفحص و الضابط بنفس الطول إشارة عدم الترقيع، لذا لا تعتمد الاستجابة على ما أُرسل وتُسحَب الإجابة التي تبدو حاسمة:
$ ./cve_2026_19490_check.py https://sp-strict.example.com
[?] https://sp-strict.example.com: INCONCLUSIVE [flat-response]
the probe and the same-length control both answered HTTP 500 / 43524, so the reply does not depend on what was sent and the fix was never exercised; unknown, not patched
مسح مجموعة أصول (--brief). صفّا gateway.example.com هما الخادمان الافتراضيان SP وIdP-only
على الجهاز نفسه — كلاهما يجيب، وهو ما لا تستطيعه أداة فحص الشروط المسبقة للإعداد:
$ ./cve_2026_19490_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE https://gateway.example.com:9443 internal-error-43524
VULNERABLE https://gateway.example.com:9444 internal-error-43524
PATCHED https://vpn.example.com fixed-error-returned
INCONCLUSIVE https://sp-strict.example.com flat-response
UNAFFECTED https://lb.example.com no-saml-endpoint
ERROR https://www.example.com not-identified
exit: 1
إخراج قابل للقراءة آلياً (--json). يُضمَّن كل طلب، لذا يمكن إعادة استنتاج النتيجة
من الأدلة بدلاً من الوثوق بها. يُسجَّل الضابط من خلال علاقته بالفحص
لا كحكم خاص به، لأن ضابطاً يبدو كإصدار مُرقَّع هو النتيجة المتوقعة على كل إصدار:
$ ./cve_2026_19490_check.py https://gateway.example.com:9443 --json
[
{
"target": "https://gateway.example.com:9443",
"verdict": "VULNERABLE",
"reason": "internal-error-43524",
"detail": "HTTP 500 / 43524: the decoded RelayState length was propagated as the internal error, so the CTX696939 fix is absent",
"netscaler_indicators": [
"CSP contains citrixng://",
"CSP contains com.citrix.nsgclient://",
"CSP contains nsgcepa://",
"CSP report-uri /nscsp_violation/report_uri",
"/vpn/js/rdx/ present (HTTP 404)"
],
"attempts": [
{
"kind": "probe",
"path": "/cgi/samlauth",
"status": 500,
"state": "unpatched",
"detail": "HTTP 500 / 43524: the decoded RelayState length was propagated as the internal error"
},
{
"kind": "control",
"path": "/cgi/samlauth",
"status": 200,
"state": "differs-from-probe",
"detail": "same-length control: HTTP 200 \"Malformed Assertion\": the fixed error was returned"
}
]
}
]
نعم. صُمِّم للاستخدام في الإنتاج والتقييم:
RelayState الخاص بالفحص إلى 20 بايت بالضبط ولا
يرسل النص البرمجي أي طول آخر تحت أي علم. على جهاز غير مُرقَّع، الطول المُفكَّك هو
مُحدِّد الفرع، وتشمل الفروع فروعاً تُنشئ جلسة وأخرى تُسبِّب SIGSEGV
لمحرك الحزم — ما يُعيد تشغيل NetScaler بالكامل ويُسقط حركة المرور لنحو 45 ثانية. عشرون
بايتاً يقع على فرع خطأ داخلي نظيف لا يُنشئ جلسة، ويرفض مُنشئ الحمولة
إنشاء أي طول آخر، لذا لا يمكن لتعديل في مكان آخر أن يوسّع الفحص بصمت.13.1-63.18 غير مُرقَّع دون توليد أي cores ودون إعادة تشغيل محرك الحزم. وقد شُغِّل الفحص نفسه
منذ ذلك الحين ضد فرعي الصيانة، على جانبي الإصلاح، وضد
الخوادم الافتراضية لمزوّد الخدمة وIdP-only على حد سواء، دون إعادة تشغيل محرك الحزم على أي منها.SAMLResponse
موجوداً للوصول إلى فرع RelayState، لكن محتواه غير ذي صلة، لذا فهو أربعة بايتات
من بيانات عشوائية. لا يُصدر الفرع أي cookie ولا يكتب أي إعداد.VULNERABLE أبداً بناءً على استجابة واحدة — انظر
الضابط.إذا عدّلت الفحص، فلا تُغيِّر طول
RelayStateالمُفكَّك ولا تمسح الأطوال. عشرون بايتاً أمر جوهري، والأقصر ليس أكثر أماناً. الأطوال الأخرى غير مُختبرة في أفضل الأحوال وانقطاع للجهاز في أسوأها.
ثلاثة طلبات HTTP لكل هدف، ورابع فقط عندما يُعيد الجهاز إشارة عدم الترقيع:
لا تُتبَّع عمليات إعادة التوجيه ولا يُرسَل أي cookie أبداً — يقدّم NetScaler قيمة NSC_DLGE
منتهية الصلاحية قد يحملها عميل يُعيد الإرسال إلى الطلب 4، فيحوّل POST إلى صفحة تسجيل دخول.
يُسجِّل nsppe طلب POST الخاص بـ SAML في /var/log/ns.log عند مستوى المعلومات، دون الحاجة إلى
مفتاح تصحيح، كفشل في فك تسلسل سياق nFactor بما في ذلك السطر
nFactor: ctx deserialize aaa_info, base64 decode failed. لا توجد أسطر إنشاء جلسة أو تسجيل دخول
عند هذا الطول، وهو ما يميّز الفحص عن محاولة التجاوز.
الإصلاح تعليمة واحدة. عند فشل فك تسلسل السياق، يحمل الإصدار المُعرَّض للثغرة
طول RelayState المُفكَّك كحالة الطلب؛ بينما يحمل الإصدار المُرقَّع الخطأ الثابت
0xe0005، الذي يُعرَض كـ Malformed Assertion. لذا يفصلهما طلب POST واحد:
| الاستجابة للفحص | الإصدار | الحكم |
|---|---|---|
500 Internal Server Error 43524 | غير مُرقَّع | VULNERABLE |
200 Malformed Assertion sent to Netscaler | مُرقَّع | PATCHED |
يُجيب الإصدار المُرقَّع بـ Malformed Assertion عند كل طول، وهو ما يجعل إجابة الإصدار غير
المُرقَّع أوراكل بدلاً من مصادفة.
يعتمد VULNERABLE على استجابة مميِّزة واحدة، لذا يتحقق النص البرمجي من أن الاستجابة تعتمد فعلاً
على ما أُرسل. بعد إشارة عدم الترقيع، يُعيد الطلب بـ RelayState ضابط بنفس الطول المُفكَّك
البالغ 20 بايت، ويختلف فقط في الـ 16 بايتاً بعد ctx=. يرفض هذا المحتوى
حارسٌ يسبق مُفكِّك التسلسل، لذا يُجيب عليه كل إصدار مُختبر — مُرقَّع وغير مُرقَّع —
بـ 200 Malformed Assertion. لا يصمد الحكم إلا إذا اختلفت الإجابتان.
وإذا تطابقتا، فهناك شيء يُعيد 500/43524 بغض النظر عن الإدخال، وتكون النتيجة
INCONCLUSIVE موسومة بـ flat-response بدلاً من VULNERABLE كاذبة. إبقاء الضابط عند
نفس الطول المُفكَّك يُبقيه داخل غلاف الأمان المُتحقَّق منه ويعزل المحتوى كالمتغير الوحيد.
الحارس أحادي الاتجاه. لا يمكنه التحقق من حكم PATCHED، لأن جهازاً مُرقَّعاً فعلاً
يُجيب على الفحص والضابط بشكل متطابق — وهذا ما يعنيه أن يكون مُرقَّعاً هنا. انظر
القيود.
يعمل فك تسلسل RelayState قبل مطابقة سياسة SAML، لذا يُجيب خادم افتراضي يحمل فقط
samlIdPPolicy على الفحص، وليس فقط خادماً مرتبطاً بـ samlAction لمزوّد خدمة. هذا تغطية
أوسع مما يمكن أن تقدّمه أداة فحص الشروط المسبقة للإعداد.
الإجابة المُعرَّضة للثغرة هي الوحيدة ذاتية التأكيد: فهي تحمل علامة محددة،
ويُثبت الضابط أن الرد يعتمد على ما أُرسل. PATCHED دليل إيجابي أيضاً — خطأ الجهاز
الثابت على المسار الذي وصل إليه الفحص — لكنه محصور بهذه الثغرة وبهذا
المسار. INCONCLUSIVE وERROR ليسا دليلاً على الإطلاق: لم يصل الفحص أبداً إلى
مُفكِّك التسلسل، لذا حالة الترقيع غير معروفة، ولهذا INCONCLUSIVE حكم منفصل
بدلاً من دمجه في PATCHED. الإبلاغ عن الصمت كنجاح يُخطئ في الاتجاه
الخطير. حيث تحتاج إلى اليقين، تأكّد بـ show ns version مقابل الإصدارات في
المعالجة.
يحمل كل حكم وسم reason قصيراً. يطبعه --brief كالعمود الثالث ويحمله --json
كـ reason.
جميع أسباب INCONCLUSIVE الأربعة تعني الشيء نفسه لاتخاذ القرار — غير معروف، وليس مُرقَّعاً.
يسمّي الوسم الشرط الذي يجب إصلاحه قبل إعادة التشغيل.
| الرمز | المعنى |
|---|---|
0 | لم يكن أي هدف VULNERABLE |
1 | هدف واحد على الأقل VULNERABLE |
2 | خطأ في الاستخدام (وسائط خاطئة / ملف أهداف غير قابل للقراءة) |
الخروج بـ 0 ليس شهادة سلامة. تنطوي فيه PATCHED وUNAFFECTED وجميع أسباب
INCONCLUSIVE الأربعة، وواحد فقط منها نتيجة إيجابية. أي غلاف يحتاج إلى التمييز
بين "مُرقَّع" و"تعذّر تصنيفه" يجب أن يقرأ الحكم — العمود الأول من --brief، أو حقل
verdict في --json — لا رمز الخروج.
PATCHED. Malformed Assertion هو ما يُعيده
الجهاز كلما فشل الفحص في الوصول إلى مُفكِّك تسلسل السياق، وكونه مُرقَّعاً هو
سبب واحد فقط لحدوث ذلك. لذا فإن جهازاً مُتداخلاً يُزيل أو يُشوِّه أو يقصر
المعامل RelayState يُنتج PATCHED على جهاز مُعرَّض للثغرة، ولن يفصل بينهما أي طلب إضافي
يمكن للأداة إرساله. حيث قد يوجد شيء أمام الجهاز، تأكّد بـ show ns version.UNAFFECTED محصور بالإصدارات الحالية. على 14.1-43.55 / 13.1-61.27 وما قبلها
تنص النشرة على أن التعرّض لا يتطلب إجراء SAML على الإطلاق — أي خادم Gateway أو AAA افتراضي
مُتأثر — لذا فإن رمز 404 على مثل هذا الإصدار لا يُخرج الجهاز من النطاق. هذا الفرع
مأخوذ من النشرة لا من القياس.PATCHED شيئاً عن أي ثغرة
NetScaler أخرى، بما في ذلك CVE-2026-19489 في النشرة نفسها.VULNERABLE أن الإصلاح
غائب على المسار الذي وصل إليه الفحص. لا يقيس إلى أي مدى يمكن لمهاجم أن يمضي في
التجاوز على إعدادك، ولن يخبرك ما إذا كان أي شخص قد فعل ذلك بالفعل — ابحث عن ذلك في
بشكل منفصل.قم بالترقية إلى 13.1-63.21 أو أحدث، أو 14.1-73.32 أو أحدث (FIPS وNDcPP: 13.1-37.277 لـ 13.1-FIPS و13.1-NDcPP، 14.1-73.32 FIPS لـ 14.1-FIPS)، وفقاً لـ CTX696939. الأجهزة على 12.1 أو 13.0 ليس لها إصلاح ولن تحصل عليه — هذان الفرعان في نهاية العمر الافتراضي ويجب ترحيلهما إلى فرع مدعوم.
ثلاث ملاحظات إضافية:
defaultAuthorizationAction لأي vpn sessionAction مرتبط بالـ vserver — بل ترجع إلى
العام set vpn parameter -defaultAuthorizationAction. الافتراضي المُشحون هو DENY؛ وALLOW
شائع في الميدان لأنه ما يلجأ إليه المسؤولون بدلاً من كتابة سياسات
تفويض لكل مستخدم، وALLOW هو ما يسمح للجلسة غير المُصادَقة بالوصول إلى الموارد
الداخلية. إنه عام، لا لكل vserver. هذا يحدّ من التأثير؛ وليس إصلاحاً.add authentication samlAction وadd authentication samlIdPProfile إلى جانب
add authentication vserver وadd vpn vserver — رهناً بتحذير الإصدارات الأقدم أعلاه.يُصلح CTX696939 أيضاً CVE-2026-19489، ويتداخل هذا السطح بشكل كبير مع CVE-2026-8452 من النشرة السابقة: على الإصدارات من 14.1-43.56 / 13.1-61.28 فصاعداً، كلتا المشكلتين مشروطة بإجراء SAML مُهيَّأ، لذا فإن تدقيق إعداد واحد يحدّد نطاق كليهما.
يُوزَّع هذا الكود بموجب ترخيص MIT.
استخدام هذه الأداة لمهاجمة أهداف دون موافقة متبادلة مسبقة غير قانوني. تقع على عاتق المستخدم النهائي مسؤولية الالتزام بجميع القوانين المحلية والولائية والفيدرالية المعمول بها. لا يتحمل المطوّرون أي مسؤولية وليسوا مسؤولين عن أي إساءة استخدام أو ضرر ناتج عن هذا البرنامج.
| العلم | الوصف |
|---|
TARGET | هدف واحد أو أكثر بصيغة [https://]HOST[:PORT]؛ المخطط الافتراضي هو https:// |
-f, --targets-file FILE | قراءة الأهداف من ملف (واحد في كل سطر؛ تعليقات #) |
--timeout SECS | المهلة لكل طلب (الافتراضي: 15) |
--workers N | الأهداف المتزامنة (الافتراضي: 16)؛ يبقى الإخراج بترتيب الإدخال |
-b, --brief | سطر واحد مُحاذى لكل هدف — مثالي لفحص العديد من المضيفين |
--json | إخراج JSON مُهيكل، بما في ذلك كل طلب يُرسَل لكل هدف |
--no-color | تعطيل الإخراج الملوّن (يحترم أيضاً NO_COLOR وغير TTY) |
| # | الطلب | الغرض |
|---|
| 1 | GET /logon/LogonPoint/tmindex.html | تعريف NetScaler (رموز Content-Security-Policy) |
| 2 | GET /vpn/js/rdx/core/rdx.js | تعريف NetScaler |
| 3 | POST /cgi/samlauth | الفحص |
| 4 | POST /cgi/samlauth | الضابط، يُرسَل فقط بعد إشارة عدم الترقيع |
| الحكم | وسم السبب | المعنى |
|---|
VULNERABLE | internal-error-43524 | نشر الجهاز طول RelayState المُفكَّك كخطئه الداخلي، وأثبت ضابط بنفس الطول أن الرد يعتمد على ما أُرسل. إصلاح CTX696939 غائب — رقّعه. |
PATCHED | fixed-error-returned | أعاد الجهاز الخطأ الثابت على المسار الذي وصل إليه الفحص. محصور بهذه الثغرة، وانظر القيود بشأن الأجهزة المُتداخلة. |
UNAFFECTED | no-saml-endpoint | أعاد /cgi/samlauth رمز 404، لذا لا يُخدَم مُستقبِل assertion الخاص بـ SAML هنا. لكل vserver، لا لكل جهاز، وانظر تحذير الإصدارات الأقدم في القيود. |
INCONCLUSIVE | flat-response | أجاب الفحص والضابط بنفس الطول بشكل متطابق، لذا لا يعتمد الرد على ما أُرسل. تفعيل حارس الإيجابيات الكاذبة. |
INCONCLUSIVE | generic-internal-error | أعادت النقطة الطرفية 43549، خطأها الداخلي العام، الذي تُعيده الإصدارات المُرقَّعة وغير المُرقَّعة معاً. ليس المُميِّز — ولاحظ أنه هو علامة عدم الترقيع للثغرة غير ذات الصلة CVE-2026-8452، لذا يبدو كإصابة وليس كذلك. |
INCONCLUSIVE | unrecognized-reply | أجابت النقطة الطرفية بشيء خارج المجموعة المعروفة. |
INCONCLUSIVE | no-probe-response | أجاب المضيف على التعريف لكن ليس على الفحص — مهلة، أو إعادة تعيين، أو وسيط يُسقط طلب POST. أعد المحاولة. |
ERROR | not-identified | لم يُعرَّف كـ NetScaler، أو غير قابل للوصول. |
/var/log/ns.log