Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2022-3602 — غوص تقني ومضاد لإثبات المفهوم لـ CVE-2022-3602، وهو تجاوز سعة مخزن مؤقت في punycode في OpenSSL 3.0.x، مع سكربتات إعادة الإنتاج وتحليل المكدس وتقييم تخفيف المترجم. | Kitploit
أدوات/GitHubGitHub/colmmacc/cve-2022-3602
تحليل الثغرات الأمنيةالاستغلالالتشفيرتحليل الملفات الثنائيةالأوراق والأبحاثالتعلم والتعليم
GitHubcolmmacc/cve-2022-3602

CVE-2022-3602

غوص تقني ومضاد لإثبات المفهوم لـ CVE-2022-3602، وهو تجاوز سعة مخزن مؤقت في punycode في OpenSSL 3.0.x، مع سكربتات إعادة الإنتاج وتحليل المكدس وتقييم تخفيف المترجم.

عرض المستودع
1693037منذ 3 سنواتتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE−2022-3602

ما هذا؟

هذا المستند والمستودع هو توثيق لـ CVE−2022-3602، وهي مشكلة تجاوز سعة المخزن المؤقت (buffer overflow) في ترميز punycode داخل OpenSSL. إنها "مضادّة لإثبات الفكرة" (anti-POC) (يبدو أن المشكلة غير قابلة للاستغلال) مخصصة للأشخاص الذين يحتفظون ببنيات OpenSSL الخاصة بهم ولمطوّري المترجمات (compilers).

هناك CVE منفصلة في نفس الإصدار، وهي CVE-2022-3786، والتي تؤدي أيضًا إلى تجاوزات في سعة المخزن المؤقت، لكن في تلك الحالة لا يمكن للمهاجم التحكم في المحتوى. لا يوجد استنساخ (reproduction) لتلك المشكلة هنا، لكن تلك المشكلة قد تؤدي إلى حرمان من الخدمة (Denial of Service) بسبب الانهيار (crash).

الانهيارات وتجاوزات سعة المخزن المؤقت ليست أمرًا جيدًا أبدًا، وإذا كنت تستخدم OpenSSL 3.0.x، فمن الحكمة التحديث في أقرب وقت ممكن.

لا تتردد في الإبلاغ عن أي أخطاء أو سهو عبر مشاكل GitHub (issues) أو طلبات السحب (pull-requests).

ما هي المشكلة؟

هناك خطأ بمقدار واحد (off-by-one) في طريقة تعامل ossl_punycode_decode مع فك ترميز punycode يؤدي إلى تجاوز سعة بمقدار 4 بايت. لا تكون هذه المشكلة منطقية إلا عندما يعالج OpenSSL سلسلة شهادات، وتتطلب شرطين. أولاً، يجب أن تحتوي شهادة CA أو شهادة وسيطة (Intermediary) في السلسلة على حقل قيد اسم (name-constraint) يستخدم punycode.

root@kitploit:~
nameConstraints = permitted;email:xn-maccrthaigh-n7a.com

ثانيًا، يجب أن تحتوي الشهادة الطرفية (leaf certificate) على حقل otherName من نوع SubjectAlternateName (SAN) يحدد سلسلة SmtpUTF8Mailbox.

root@kitploit:~
otherName = 1.3.6.1.5.5.7.8.9;UTF8:[email protected]

عند التشغيل، سيتم التعامل مع punycode الموجود في حقل nameConstraints، وليس punycode الموجود في حقل otherName، بواسطة محلل punycode الضعيف في OpenSSL.

ما مدى سهولة تشغيل هذه المشكلة؟

حدد David Benjamin و Matt Caswell أن فحص nameConstraint يحدث بعد التحقق العادي من سلسلة الشهادات والتحقق من التوقيع. بالنسبة لمعظم التطبيقات، هذا يعني أنه لا يمكن تشغيل المشكلة بشهادة موقّعة ذاتيًا أو سلسلة غير صالحة.

لاحظ أن تطبيقي s_client و s_server من openssl مخصصان للتصحيح (debugging) ولا يتوقفان عن المعالجة عندما تكون السلسلة غير صالحة.

يجب أن تحتوي شهادة CA أو شهادة وسيطة موثوقة على الحمولة الخبيثة، ويجب أيضًا أن تكون قد وقّعت الشهادة الطرفية التي تشغّل المشكلة.

قد توجد بعض البيئات التي تكون فيها الأطراف غير الموثوقة هي الـ CA أو الوسطاء (Intermediaries)، على سبيل المثال خدمة استضافة تدعم شهادات CA خاصة مقدمة من العملاء، لكن هذا ليس شائعًا.

هل تؤدي المشكلة إلى تنفيذ التعليمات البرمجية عن بُعد (RCE)؟

ستكون الإجابة بالنسبة للعديد من التطبيقات "لا" بسبب كيفية تخطيط المترجم للمكدس (stack) ووجود حمايات أخرى مثل حراس المكدس (stack canaries) / ملفات تعريف الارتباط للمكدس (stack cookies)، والحشو (padding)، وPIE، وFORTIFY_SOURCE.

المشكلة تؤدي بالفعل إلى تجاوز سعة بمقدار 32-بت على المكدس. هذا ليس كافيًا لتنفيذ كود الصدفة (shell code) مباشرة، لكنه قد يكون كافيًا لتغيير تدفق التحكم (control flow) في التطبيق. على سبيل المثال، قد يكون القفز إلى كود صدفة مضمّن في سلسلة شهادات X509 ممكنًا إذا تم أيضًا نسخ هذه البيانات على المكدس في موقع قابل للتنفيذ.

على كل منصة لينكس اختبرتها، يحدث التجاوز في الحشو (padding) ويكون غير ضار. نظريًا، قد يخطّط المترجم المتغيرات بحيث يحدث التجاوز في أحد المتغيرات الأخرى في دالة ossl_a2ulabel.

اعتمادًا على التضمين (inlining)، القائمة الكاملة للمتغيرات الموجودة هي:

root@kitploit:~
outptr, inptr, size, result, tmpptr, delta, seed, utfsize

ولا يبدو لي أن أيًا منها يوفر مسارًا واضحًا لتصعيد الامتيازات (privilege escalation) أو تحكمًا مثيرًا للاهتمام.

لقد أرفقت ملفًا مضغوطًا (tarball) يحتوي على أدوات يمكن استخدامها لإنشاء استنساخات (reproductions) وتجاوزات بأقصى قدر ممكن من التحكم في جميع البايتات الأربعة. سلسلة الاستنساخ المرجعية ( xn--ww90271...aaaa) تتجاوز البايتات الأربعة بالقيم 0xFF 0x0F 0x0F 0x0F. إذا لم يؤدِ ذلك إلى انهيار التطبيق، فمن المحتمل (likely؟) أن هذا التطبيق ليس ضعيفًا (not vulnerable).

كيف يمكنني إعادة إنتاج هذه المشكلة؟

يمكن استخدام النص البرمجي (script) run-poc لإنشاء سلسلة شهادات خبيثة. يتم إنشاء شهادة CA خبيثة من ca.cnf ويتم إنشاء شهادة طرفية محفّزة من leaf.cnf.

تستخدم شهادة CA الحمولة المرجعية التالية:

xn--ww902716aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

يمكن استخدام سكربت بايثون لتوليد سلاسل punycode أخرى لحمولات مختلفة.

عند تنفيذه، سيشغّل run-poc عميلًا وخادمًا من openssl ويحاول استغلال المشكلة عشر مرات.

من المرجح أن ينهار (crash) OpenSSL الضعيف. هذا لا يعني أن إصدار OpenSSL معرّض لـ RCE، لأن حراس المكدس وحمايات ملفات تعريف الارتباط للمكدس تسبب أيضًا عادةً انهيارًا (أكثر أمانًا) للتطبيق. لاحظ أيضًا أن هذا لا يغيّر من شدة الـ CVE الأخرى في نفس الإصدار.

كيف تعمل هذه المشكلة؟

من المدهش أن الحصول على تحكم شبه كامل في جميع البايتات الأربعة للتجاوز يتطلب دقة متناهية، ويتطلب استغلال مفكك ترميز punycode في OpenSSL بترميز punycode غير قياسي / غير صالح. يحتوي الملف المضغوط المرفق على سكربت يمكنه بناء سلسلة تتعامل مع هذه الدقة. فيما يلي شرح لكيفية عملها.

تجهيز المرحلة

المشكلة الأمنية موجودة في ossl_punycode_decode()

root@kitploit:~
int ossl_punycode_decode(const char *pEncoded, const size_t enc_len,
                         unsigned int *pDecoded, unsigned int *pout_length)

يتم استدعاء ossl_punycode_decode من ossl_a2ulabel. مخزن pEncoded هو مخزن بحجم تعسفي إلى حد كبير يأتي من سلسلة شهادات X509. إنه الجزء الذي يأتي بعد أي "xn--" في حقل nameConstraint. راجع [reproduction] لمعرفة كيفية إعادة إنتاج مثل هذه السلسلة من الشهادات.

pDecoded هو مصفوفة من unsigned int بحجم LABEL_BUF_SIZE. LABEL_BUF_SIZE هو 512، وفي معظم المنصات سيكون unsigned int بعرض 4 بايت. لذا في معظم المنصات يكون طول pDecoded هو 2048 بايت.

المشهد

داخل ossl_punycode_decode()، جوهر المشكلة هو فحص الطول غير الصحيح التالي:

root@kitploit:~
 if (written_out > max_out)

max_out يقابل *pout_length وهو دائمًا 512. وwritten_out يتتبع عدد عناصر unsigned int التي تمت كتابتها إلى pDecoded. نظرًا لأنه تتم زيادة written_out لاحقًا فقط بعد الكتابة، فإن هذا الفحص المعيب يسمح بكتابة 513 عنصرًا من unsigned int إلى pDecoded. النتيجة النهائية تبدو شيئًا كهذا ...

root@kitploit:~
pDecoded = [ ... , 'X , 'Y' , 'Z' ] 'P'
// Indices         509   510   511

هنا، وفقًا لاتفاقية لغة C، تكون المؤشرات مفهرسة من الصفر، لذا فإن الفتحة رقم 511 هي العنصر رقم 512 في المصفوفة. 'P' هي حمولة من أربعة بايت تم وضعها خارج الحدود، خارج المساحة المخصصة على المكدس لمخزن buf في ossl_a2ulabel() والذي يشير إليه pDecoded.

أربعة بايت هي تجاوز صغير، ولا تكفي لحمل منزلق nop (nop-sled) أو تنفيذ كود الصدفة مباشرة، لكنها كافية لتغيير تدفق التحكم في التطبيق. على سبيل المثال، قد يكون القفز إلى كود صدفة مضمّن في سلسلة شهادات x509 ممكنًا، اعتمادًا على كيفية تخزين هذه البيانات (أو أجزاء منسوخة من هذه البيانات) وما إذا كانت تلك الذاكرة قابلة للتنفيذ. ومع ذلك، لا تزال هناك صعوبة أكبر أمام المهاجم المحتمل.

أولاً، حشو المترجم ومحاذاة المكدس، أو الدفاعات مثل حراس المكدس (stack canaries)، قد تجعل أي استغلال مستحيلًا تمامًا.

ثانيًا، هناك مسار واحد فقط إلى ossl_punycode_decode()، وهذا المسار يستخدم مخزنًا على المكدس. هذا يجعل من غير المرجح استخدام المشكلة لتجاوزات متزامنة بمقدار 4 بايت في مواقع ذاكرة مختلفة.

فك ترميز Punycode

سلاسل Punycode لها أساسًا شكلان. أحدهما xn--c1yn36f (點看) والآخر xn--maccrthaigh-n7a (maccárthaigh). الجزء الذي يأتي بعد آخر فاصل - هو ترميز bootstring بأساس 36 لأي نقاط رمز يونيكود (unicode code-points) ليست ascii أساسية عادية، مع موضع السلسلة لإدراجها. المهم الآن هو أن عملية فك الترميز في ossl_punycode_decode() تنتج قيمتين. إحداهما 'n' وهي قيمة unsigned int لنقطة الرمز التي سيتم إدراجها، والأخرى 'i' وهي الموضع في المخزن لإدراجها.

يمكن أن تحدث الكتابة بطريقتين مختلفتين. إذا كانت i في مكان ما في منتصف السلسلة، فهناك memmove() التي "تصنع مساحة" أولاً عن طريق نسخ كل شيء إلى اليمين بمقدار فتحة واحدة:

root@kitploit:~
memmove(pDecoded + i + 1, pDecoded + i,
       (written_out - i) * sizeof *pDecoded);

ثم تكتب n في المساحة التي صنعتها للتو:

root@kitploit:~
 pDecoded[i] = n;

إذا كانت i في نهاية السلسلة، فلن يكون لـ memmove() أي تأثير لأن المعامل الأخير سيكون 0. يصبح السطر الآخر إلحاقًا بسيطًا.

الآن سننظر في الطرق الثلاث المختلفة الموجودة للحصول على حمولة 'P' في موضع التجاوز ولماذا تنشأ القيود.

الطريقة 1 - تجاوز ascii

أبسط طريقة لتشغيل التجاوز هي صياغة سلسلة punycode تحتوي على 511 حرفًا من ascii وحرفين غير ascii. ترميز punycode لسلسلة بطول 513 حرفًا مثل "ÁÁAAAAAAAA...AAA" سيفي بالغرض. في هذه الحالة، ما سيحدث هو أنه عندما تكون written_out هي 510 سيكون لدينا مخزن مرتب على النحو التالي ...

root@kitploit:~
pDecoded = [ 'A' , 'A' ,  ... , 'A' , 'A',     ]
// Indices    0     1     ...   509   510  511

هذه مجرد أحرف ascii الأساسية التي تم نسخها. ثم نحلل bootstring الخاص بـ punycode وندرج 'Á' في الموضع 0. على الرغم من أنها قد تكون أي موضع بين 0 و511 شاملة.

root@kitploit:~
pDecoded = [ 'Á' , 'A' ,  ... , 'A' , 'A', 'A' ]
// Indices    0     1     ...   509   510  511

ثم نكرر هذا:

root@kitploit:~
pDecoded = [ 'Á' , 'Á' ,  ... , 'A' , 'A', 'A' ] 'A'
// Indices    0     1     ...   509   510  511   512

سيؤدي هذا إلى تجاوز حرف ascii العادي 'A' حيث يتم "نقله". تصبح حمولة الأربعة بايت في هذه الحالة 0x00 0x00 0x00 0x41. كما سنرى، بسبب كيفية عمل punycode، هذه هي الطريقة الوحيدة التي يمكن بها التعبير عن أي قيمة ببايت أخير في نطاق ascii.

نحتاج إلى استخدام حرفين غير ascii لأن هناك فحص حدود صحيح لعدد الأحرف الأساسية، لذا يجب أن يكون أقل من 512.

هناك قيد إضافي وهو أن قيمة البايت الأخير لا يمكن أن تكون 46، وينشأ ذلك لأن ossl_punycode_decode() تُستدعى على الجزء من السلسلة الذي يسبق حرف . حرفيًا. Punycode مخصص لتسميات النطاقات (domain labels)، والتي لا يمكن أن تحتوي على نقاط فيها.

الطريقة 2 - تجاوز مباشر غير ascii

الطريقة التالية الأبسط لتشغيل التجاوز هي صياغة سلسلة من 513 حرفًا بحرف غير ascii في نهايتها تمامًا. شيء مثل "AAAAAAAAAA...AAÁ". في هذه الحالة، في خطوتينا الأخيرتين سيكون لدينا:

pDecoded = [ 'A' , 'A' , ... , 'A' , 'A' ] // Indices 0 1 ... 510 511

و:

root@kitploit:~
pDecoded = [ 'A' , 'A' ,  ... , 'A' , 'A' ] 'Á'
// Indices    0     1     ...   510   511

سيذهب الحرف غير ascii مباشرة إلى موضع التجاوز. لا يفرض محلل punycode في OpenSSL أن قيمة التجاوز هنا هي حرف يونيكود صالح فعليًا. إنها إلى حد كبير عملية فك ترميز ثنائية. لكن الفروق الدقيقة في فك ترميز punycode تعني أن الطريقة 2 ليست مرنة كما قد تبدو للوهلة الأولى.

في punycode، يتم ترميز القيمتين n وi معًا كعدد صحيح واحد متغير الطول يتم ترميزه بعد ذلك بنظام العد الأساس 36 (base36). قد يبدو من المستحيل ترميز رقمين غير مرتبطين كعدد صحيح واحد، لكن الحيلة الذكية في punycode هي استخدام طول السلسلة (حتى الآن) كحقل مخفي.

على سبيل المثال، لنفترض أن لدينا سلسلة punycode بها 4 أحرف أساسية، وحرف واحد غير أساسي، مثل AAÁAA. سيتم تمثيل ذلك أولاً بالأحرف الأساسية فقط ... AAAA. قيمة يونيكود 'Á' هي 225 وموضعها في السلسلة هو 2. الحيلة هي ضرب القيمة في الطول زائد واحد، ثم إضافة الموضع. فيصبح ((225 * (4 +1)) + 2) وهو 1127، وهكذا يتم ترميزها (في base36 متغير الطول).

لفك الترميز، تسير في الاتجاه الآخر. 1127 / 5 يساوي 225 و1127 % 5 يساوي 2. هكذا تسترجع رقمين من رقم واحد. لكن لاحظ أنه كلما طالت السلسلة، أصبحت أكثر تقييدًا في حجم القيمة، وإلا فلن يتسع المضاعف في unsigned int. بشكل عام، إذا كان طول السلسلة M حرفًا، فإنك تفقد عرضًا بمقدار log M بت من القيمة.

بحلول الوقت الذي تتعامل فيه مع العدد الصحيح رقم 512، تفقد 9 بت من العرض. باستخدام الطريقة 2، أعلى قيمة يمكن أن تصل إليها حمولة تبدو 32-بت هي في الواقع 2^23. ليس حتى ثلاثة بايت كاملة. الطريقة 2 دون المثلى.

الطريقة 3 - الحشو (stuffing)

لاستعادة 4 بايت من التحكم، الطريقة الأكثر كفاءة هي تكرار حرف الحمولة مرارًا وتكرارًا. حتى الآن تركت تفصيلين آخرين ذوي صلة بكيفية التعامل مع punycode.

التفصيل الأول هو أن الأحرف غير ascii لا تُرمَّز بترتيب السلسلة، بل تُرمَّز بترتيب تصاعدي للقيمة. السلسلة "ÉÁ" سيتم ترميزها في النهاية كـ "Á في الموضع 1، É في الموضع 0" لأن Á لها قيمة أقل (225) من É (233).

التفصيل الثاني هو أن الأحرف غير ascii لا تُرمَّز بقيمها الحرفية، بل كدلتا (delta) نسبية إلى آخر قيمة تم فك ترميزها. نظرًا لأن القيمة الأولى ليس لها قيمة سابقة تكون نسبية إليها، فهناك نقطة بداية ثابتة (hard-coded) بقيمة 128.

هذه الفروق الدقيقة الصغيرة تجعل punycode فعالًا جدًا في المساحة، ولكنها تعني أيضًا أن الحرف غير ascii ببساطة لا يمكن فك ترميزه إلى قيمة أقل من 128. أصغر دلتا هي 0، ولا توجد طريقة للتعبير عن دلتا سالبة. لذا إذا كنت تريد رقمًا أقل من 128، فعليك استخدام الطريقة 1.

كما أنها تعني أن أفضل استراتيجية للحصول على أكبر قدر ممكن من التحكم في الحمولة هي جعل الحمولة هي القيمة الوحيدة في السلسلة الكاملة، وبهذه الطريقة نحصل على العرض الكامل للعمل به من مكانها في الموضع 0 في الترميز. السلسلة التي ترمّزها تبدو في النهاية هكذا:

root@kitploit:~
 [ 'P', 'P', ... 'P', 'P', 'P' ]
    0    1       510  511  512

والتي سيتم فك ترميزها بواسطة OpenSSL على النحو التالي ...

root@kitploit:~
pDecoded = [ 'P', 'P', ... 'P', 'P' ] 'P'
              0    1       510  511   512

مع P في موضع التجاوز، وقادرة على تمثيل أي قيمة بين 128 و (2^32 - 1).

كل هذا يتطلب مشفر punycode غير قياسي، وقد أدرجت سكربتًا يمكنه صياغة حمولة باستخدام الطريقة 1 أو الطريقة 3 حسب الحاجة.

الأسئلة الشائعة المصغرة:

بصرف النظر عن تحديث OpenSSL، هل هناك تخفيفات أخرى؟

يتم تمرير سلاسل الشهادات كنص واضح (clear-text) في معظم البيئات، ويمكن حظر سلسلة خبيثة عن طريق رفض اتصالات TCP التي تحتوي على NID بترميز DER وهو 1.3.6.1.5.5.7.8.9 في حقل OtherName من SubjectAlternateName.

لسوء الحظ، يمكن تقسيم هذا الحقل بشكل تعسفي بين حزمتين أو أكثر، وهناك حاجة فعلًا إلى نوع من مطابقة الأنماط ذات الحالة (stateful pattern matcher) للحظر. يمكن أيضًا ضغط الشهادات، لكن OpenSSL 3.0.x لا يدعم ضغط الشهادات في الوقت الحالي.

بالإضافة إلى ذلك، مع TLS1.3 يتم تشفير سلاسل شهادات العميل على الشبكة، وتدعم الإصدارات السابقة من TLS سلاسل الشهادات المشفرة عند إعادة التفاوض على اتصال قائم. يتم ذلك أحيانًا لمصادقة الشهادات التي يبدأها الخادم. لن يكون مرشح الشبكة فعالًا في تلك الحالات.

كيف يمكنني معرفة ما إذا كنت أستخدم openssl 3 في ملف ثنائي مرتبط بشكل ثابت (statically linked)؟

root@kitploit:~
 readelf -a [binary] | grep -i ossl_punycode_decode

سيبحث عن الدالة الضعيفة في ملف ثنائي مرتبط بشكل ثابت. فقط OpenSSL >= 3.0 يحتوي على هذه الدالة.

تنزيل الأداة