أمان تطبيقات C++: أهم 10 ثغرات وطرق الحماية
C++ هي لغة برمجة أنظمة قوية، لكن مرونتها والوصول المباشر للذاكرة يجعلان التطبيقات عرضة لهجمات خطيرة. وفقًا لـ OWASP و CWE، تنشأ العديد من الثغرات الحرجة في البرامج الصناعية (من الألعاب إلى أنظمة الطيران) بسبب أخطاء في كود C++. في هذه المقالة، سنستعرض أهم 10 ثغرات خطيرة، ونقدم أمثلة على الكود، ونوضح كيفية تجنبها.
1. تجاوز سعة المخزن المؤقت (Buffer Overflow)
ثغرة كلاسيكية تسمح للمهاجم بإعادة كتابة مناطق الذاكرة المجاورة. تنشأ عند نسخ البيانات دون التحقق من الحجم.
// كود ضعيف#include <cstring>void vulnerable(char* input) { char buffer[10]; strcpy(buffer, input); // خطر: إذا كان input أطول من 9 أحرف}// نسخة آمنةvoid secure(char* input, size_t len) { char buffer[10]; if (len < sizeof(buffer)) { strncpy(buffer, input, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\\0'; }}الحماية: استخدم std::string أو std::vector أو الدوال ذات الحد الأقصى للطول (strncpy، snprintf). قم بتفعيل أعلام المترجم -fstack-protector-strong و -D_FORTIFY_SOURCE=2.
2. الاستخدام بعد التحرير (Use-After-Free)
الوصول إلى ذاكرة تم تحريرها بالفعل. يؤدي إلى القراءة/الكتابة في منطقة غير مملوكة أو تنفيذ كود عشوائي.
// كود ضعيفint* ptr = new int(42);delete ptr;*ptr = 100; // خطأ! ptr غير صالح الآن// نسخة آمنةint* ptr = new int(42);delete ptr;ptr = nullptr; // نعيد تعيين المؤشر للصفرif (ptr) *ptr = 100; // التحقق قبل الاستخدامالحماية: استخدم المؤشرات الذكية (std::unique_ptr، std::shared_ptr)، وأعد تعيين المؤشرات الخام للصفر بعد delete، وطبق أدوات التعقيم (-fsanitize=address).
3. تجاوز سعة الأعداد الصحيحة (Integer Overflow)
تجاوز الحدود المسموح بها في العمليات الحسابية. يُستخدم غالبًا لتجاوز فحوصات حجم المخزن المؤقت.
// كود ضعيفsize_t size = 0;size_t user_input = 4294967295; // 2^32 - 1size_t total = size + user_input + 1; // تجاوز!// نسخة آمنة مع التحققif (user_input > SIZE_MAX - size) { throw std::overflow_error("الحجم كبير جدًا");}size_t total = size + user_input + 1;الحماية: استخدم أنواعًا ذات عرض بت ثابت (uint32_t)، وتحقق من التجاوز قبل العملية، وطبق __builtin_add_overflow (GCC/Clang).
4. إلغاء الإشارة إلى مؤشر فارغ (Null Pointer Dereference)
محاولة الوصول إلى الذاكرة عبر عنوان NULL. يؤدي إلى إنهاء البرنامج بشكل مفاجئ (segfault) أو إلى ثغرة DoS.
// كود ضعيفvoid process(int* ptr) { *ptr = 10; // إذا كان ptr == nullptr — تعطل}// نسخة آمنةvoid process(int* ptr) { if (!ptr) { throw std::invalid_argument("تم استلام مؤشر فارغ"); } *ptr = 10;}الحماية: تحقق دائمًا من المؤشرات للتأكد من أنها ليست nullptr قبل الاستخدام، وأرجع std::optional بدلاً من المؤشرات الخام.
5. تسرب الذاكرة (Memory Leaks)
فقدان الوصول إلى الذاكرة المخصصة دون تحريرها. مع مرور الوقت، يستنزف موارد النظام.
// كود ضعيفvoid leak() { int* data = new int[100]; // نسينا delete[] data — تسرب}// نسخة آمنةvoid no_leak() { std::unique_ptr<int[]> data = std::make_unique<int[]>(100); // سيتم تحرير الذاكرة تلقائيًا}الحماية: استخدم RAII (المؤشرات الذكية، الحاويات