
ثغرة هارت بليد `CVE-2014-0160` هي عيب تنفيذي خطير في مكتبة OpenSSL، تُمكّن المهاجمين من سرقة البيانات من ذاكرة الخادم الضحية. تعتمد محتويات البيانات المسروقة على ما هو موجود في ذاكرة الخادم. وقد تحتوي هذه البيانات على مفاتيح خاصة، ومفاتيح جلسات TLS، وأسماء مستخدمين، وكلمات مرور، وبطاقات ائتمان، وغير ذلك. وتكمن الثغرة في تنفيذ بروتوكول Heartbeat، الذي تستخدمه SSL/TLS لإبقاء الاتصال نشطًا.
ثغرة Heartbleed CVE-2014-0160 هي خلل تنفيذي خطير في مكتبة OpenSSL، مما يتيح للمهاجمين سرقة البيانات من ذاكرة الخادم الضحية. محتويات البيانات المسروقة تعتمد على ما هو موجود في ذاكرة الخادم. وقد تحتوي بشكل محتمل على مفاتيح خاصة، ومفاتيح جلسات TLS، وأسماء مستخدمين، وكلمات مرور، وبطاقات ائتمان، وغير ذلك. تكمن الثغرة في تنفيذ بروتوكول Heartbeat، الذي يُستخدم في SSL/TLS للحفاظ على استمرار الاتصال.
نطاق إصدارات OpenSSL المتأثرة هو من 1.0.1 إلى 1.0.1f. الإصدار في جهاز Ubuntu الافتراضي هو 1.0.1.
يعتمد هجوم Heartbleed على طلب Heartbeat. هذا الطلب يرسل ببساطة بعض البيانات إلى الخادم، ويقوم الخادم بنسخ البيانات إلى حزمة الاستجابة الخاصة به، بحيث يتم إرجاع جميع البيانات (echo). في الحالة الطبيعية، افترض أن الطلب يتضمن 3 بايتات من البيانات ”ABC“، وبالتالي فإن حقل الطول (length field) يحمل القيمة 3. سيضع الخادم البيانات في الذاكرة، وينسخ 3 بايتات من بداية البيانات إلى حزمة الاستجابة الخاصة به. في سيناريو الهجوم، قد يحتوي الطلب على 3 بايتات من البيانات، ولكن حقل الطول قد يقول 1003. عندما يُنشئ الخادم حزمة الاستجابة الخاصة به، فإنه ينسخ من بداية البيانات (أي “ABC”)، لكنه ينسخ 1003 بايتات بدلاً من 3 بايتات. هذه الـ 1000 بايت الإضافية بوضوح لا تأتي من حزمة الطلب؛ بل تأتي من الذاكرة الخاصة للخادم، وقد تحتوي على معلومات مستخدمين آخرين، ومفاتيح سرية، وكلمات مرور، وما إلى ذلك.
بعد ذلك، غيّر حقل الطول في الطلب. أولاً، دعنا نفهم كيف يتم بناء حزمة استجابة Heartbeat من الشكل أعلاه. عندما تصل حزمة طلب Heartbeat، يقوم الخادم بتحليل الحزمة للحصول على الحمولة (payload) وقيمة Payload_length (وهي مميزة بالأعلى). هنا، الحمولة هي مجرد سلسلة من 3 بايتات وقيمة هي بالضبط 3. برنامج الخادم سيأخذ قيمة الطول هذه بشكل أعمى من حزمة الطلب. ثم يبني حزمة الاستجابة بالإشارة إلى الذاكرة التي تخزن ونسخ بايتاً إلى حمولة الاستجابة. بهذه الطريقة، ستحتوي حزمة الاستجابة على سلسلة من 3 بايتات .
"ABC"Payload_length"ABC"Payload_length"ABC"بعد ذلك، قم بتشغيل هجوم Heartbleed كما هو موضح في الشكل أدناه. أبقِ نفس الحمولة (3 بايتات)، ولكن اضبط حقل Payload_length على 1003. سيأخذ الخادم مرة أخرى قيمة Payload_length هذه بشكل أعمى عند بناء حزمة الاستجابة. هذه المرة، سيشير برنامج الخادم إلى السلسلة "ABC" وينسخ 1003 بايتات من الذاكرة إلى حزمة الاستجابة كحمولة. بالإضافة إلى السلسلة ”ABC“، يتم نسخ الـ 1000 بايت الإضافية إلى حزمة الاستجابة، وقد تكون أي شيء من الذاكرة، مثل نشاط سري، أو معلومات تسجيل، أو كلمة مرور، وما إلى ذلك.
يتيح كود الهجوم تغيير قيمة Payload_length. بشكل افتراضي، يتم تعيين القيمة إلى قيمة كبيرة جداً (0x4000)، ولكن يمكن تقليلها.
أسهل طريقة لإصلاح ثغرة Heartbleed هي تحديث مكتبة OpenSSL إلى أحدث إصدار. ومع ذلك، الهدف هو تصحيح الثغرة عبر الكود المصدري.
صيغة حزمة طلب/استجابة Heartbeat
struct {
HeartbeatMessageType type; // 1 byte: request or the response
uint16 payload_length; // 2 byte: the length of the payload
opaque payload[HeartbeatMessage.payload_length];
opaque padding[padding_length];
} HeartbeatMessage;
الحقل الأول (بايت واحد) من الحزمة هو معلومات النوع، والحقل الثاني (بايتان) هو طول الحمولة، يليهما الحمولة الفعلية والحشو (padding). يجب أن يكون حجم الحمولة مساوياً للقيمة الموجودة في حقل طول الحمولة، لكن في سيناريو الهجوم، يمكن ضبط طول الحمولة على قيمة مختلفة. يُظهر مقطع الكود التالي كيف يقوم الخادم بنسخ البيانات من حزمة الطلب إلى حزمة الاستجابة.
معالجة حزمة طلب Heartbeat وتوليد حزمة الاستجابة
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
*/
unsigned int payload;
unsigned int padding = 16; /* Use minimum padding */
// Read from type field first
hbtype = *p++; /* After this instruction, the pointer
* p will point to the payload_length field */
// Read from the payload_length field from the request packet
n2s(p, payload); /* Function n2s(p, payload) reads 16 bits
* from pointer p and store the value
* in the INT variable "payload". */
pl = p; // pl points to the beginning of the payload content
if (hbtype == TLS1_HB_REQUEST)
{
unsigned char *buffer, *bp;
int r;
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
*/
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;
// Enter response type, length and copy payload *bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
// copy payload
memcpy(bp, pl, payload); /* pl is the pointer which
* points to the beginning
* of the payload content */
bp += payload;
// Random padding
RAND_pseudo_bytes(bp, padding);
// this function will copy the 3+payload+padding bytes
// from the buffer and put them into the heartbeat response
// packet to send back to the request client side.
OPENSSL_free(buffer);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
}
موطن الثغرة هنا
// copy payload
memcpy(bp, pl, payload);
لا يوجد أي تحقق لتحديد ما إذا كان pl صالحاً أم لا. لذلك، يمكن أن يحدث اختراق للذاكرة.
التصحيحات:
memcpy()تم إجراء التصحيحات في الجهاز الافتراضي ولكنها غير معروضة في هذا المستودع.
شكراً لاهتمامك، كان هذا المشروع ممتعاً ومفيداً!