عندما يلتقي C++ بالويب: لماذا الأمان هو صداعك الرئيسي
\n\nهل تساءلت يومًا لماذا يحب المتسللون تطبيقات C++ كثيرًا؟ لا، ليس لأن الكود معقد. بل لأنه في السعي وراء الأداء، غالبًا ما ينسى المطورون أمان C++. ويدفعون ثمن ذلك ببيانات المستخدمين وسمعتهم وأموالهم.
\n\nتخيل: لقد كتبت خادمًا رائعًا بلغة C++ يعالج الطلبات أسرع من البرق. لكن إدخالًا واحدًا غير مُهرب - ويقوم المهاجم بتفريغ قاعدة البيانات بأكملها. أو ما هو أسوأ، يقوم بإدراج سكريبت يسرق جلسات جميع زوار الموقع. هل هذا مألوف؟ إذا لم يكن كذلك - رائع، فهذا يعني أنك تقرأ هذه المقالة في الوقت المناسب.
\n\nفي هذه المادة، سنقوم بتحليل أخطر هجومين على تطبيقات الويب - حقن SQL والبرمجة النصية عبر المواقع (XSS) - ونوضح كيفية الحماية منهما تحديدًا في C++. بدون سحر، مع أمثلة كود واقعية. هيا بنا.
\n\nحقن SQL: كيف يحول المتسللون استعلامك إلى سلاح
\n\nحقن SQL هو عندما يقوم المهاجم بإدخال جزء من كود SQL في حقل الإدخال (مثل اسم المستخدم أو استعلام البحث). إذا قام تطبيقك بتعويض هذا الإدخال بشكل أعمى في الاستعلام، فبدلاً من البحث عن مستخدم بالاسم، ستنفذ قاعدة البيانات شيئًا مثل DROP TABLE users. أو ما هو أسوأ، ستقوم بإخراج جميع كلمات المرور.
خطأ المبتدئ النموذجي (وكيفية تجنبه)
\n\nانظر إلى هذا الكود. يبدو غير ضار، لكنه قنبلة موقوتة:
\n\n// خطير! لا تفعل هذا أبدًا\nstd::string username = getUserInput(); // المستخدم أدخل: ' OR '1'='1\nstd::string query = "SELECT * FROM users WHERE username = '" + username + "'";\n// النتيجة: SELECT * FROM users WHERE username = '' OR '1'='1'\n// سيعيد جميع المستخدمين!\n\nهل ترى المشكلة؟ إذا أدخل المستخدم ' OR '1'='1، سيعيد الاستعلام الجدول بأكمله. وإذا أدخل '; DROP TABLE users; --، فوداعًا أيتها القاعدة.
النهج الصحيح: الاستعلامات المعلمة
\n\nالطريقة الوحيدة الموثوقة للحماية من حقن SQL في C++ هي استخدام الاستعلامات المعلمة. قاعدة البيانات نفسها تميز بين الكود والبيانات. إليك كيف يبدو ذلك مع مكتبة libpqxx (لـ PostgreSQL) أو MySQL Connector/C++:
\n\n#include <iostream>\n#include <pqxx/pqxx> // libpqxx لـ PostgreSQL\n\nvoid safeQuery(const std::string& username) {\n try {\n pqxx::connection conn("dbname=mydb user=myuser password=mypass");\n pqxx::work txn(conn);\n \n // استعلام معلم: $1 هو المعامل\n std::string query = "SELECT * FROM users WHERE username = $1";\n pqxx::result result = txn.exec_params(query, username);\n \n for (auto row : result) {\n std::cout << "User: " << row["username"].c_str() << std::endl;\n }\n txn.commit();\n } catch (const std::exception &e) {\n std::cerr << "Error: " << e.what() << std::endl;\n }\n}\n\nint main() {\n std::string input;\n std::cout << "Enter username: ";\n std::getline(std::cin, input);\n safeQuery(input);\n return 0;\n}\n\nلاحظ: نحن نستخدم txn.exec_params بدلاً من دمج السلاسل النصية. قاعدة البيانات نفسها تقوم بتهريب الأحرف الخاصة. حتى إذا أدخل المستخدم ' OR '1'='1، سيتم التعامل معه كـ سلسلة نصية، وليس كجزء من أمر SQL.
ماذا تفعل إذا كنت تستخدم SQLite الخام؟
\n\nحتى مع SQLite يمكنك العمل بأمان. استخدم sqlite3_prepare_v2 و sqlite3_bind_text:
#include <sqlite3.h>\n#include <iostream>\n\nvoid safeSQLiteQuery(sqlite3* db, const std::string& username) {\n sqlite3_stmt* stmt;\n const char* sql = "SELECT * FROM users WHERE username = ?";\n \n if (sqlite3_prepare_v2