كيف تمنع المخترقين من اختراق كود جافا الخاص بك: SQL Injection و XSS تحت المجهر
\n\nتخيل: لقد كتبت تطبيق ويب مثاليًا بلغة جافا. واجهة جميلة، منطق سريع، بنية نظيفة. تقوم بنشره على الخادم، وتُحضر كوب شاي... وبعد ساعة تتلقى رسالة تفيد بأن قاعدة البيانات قد حُذفت، وعلى الصفحة الرئيسية يظهر رسم ASCII استفزازي لشخص ما. قصة مألوفة؟ للأسف، بالنسبة للعديد من المطورين — نعم.
\n\nأكثر ثغرات تطبيقات الويب شيوعًا وخطورة — SQL Injection و Cross-Site Scripting (XSS) — تحرم مسؤولي آلاف المواقع من النوم سنويًا. وجافا، على الرغم من صرامتها، ليست استثناءً. لكن هناك خبر سار: الحماية منها ممكنة. بل وليست صعبة جدًا.
\n\nفي هذه المقالة، سنشرح كيف يهاجم المخترقون تطبيقات جافا، والأهم من ذلك — كيفية إنشاء حاجز موثوق باستخدام أدوات مثبتة وعادات برمجة صحيحة. اربطوا أحزمة الأمان: سيكون هناك الكثير من الأكواد والحيل العملية والحد الأدنى من الحشو.
\n\nSQL Injection: عندما يصبح المستخدم مبرمجًا
\n\nما هو ولماذا يعمل؟
\n\nSQL Injection هي تقنية هجوم يقوم فيها المخترق بـ"حقن" كود SQL ضار في الاستعلام عبر إدخال المستخدم. إذا كان التطبيق يدمج السلاسل النصية بلا وعي، يمكن للمهاجم الوصول إلى بيانات الآخرين، أو حذف الجداول، أو حتى السيطرة على خادم قاعدة البيانات.
\n\nمثال كلاسيكي لكود سيء في جافا:
\n\nString userInput = request.getParameter("username");\nString query = "SELECT * FROM users WHERE username = '" + userInput + "'";\nStatement stmt = connection.createStatement();\nResultSet rs = stmt.executeQuery(query);\n\nماذا يحدث إذا أدخل المستخدم: admin' OR '1'='1؟ يتحول الاستعلام إلى:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'\n\nالشرط '1'='1' صحيح دائمًا. تهانينا: لقد دخل المخترق للتو إلى النظام كمسؤول، حتى دون معرفة كلمة المرور. وإذا أدخل '; DROP TABLE users; -- — فقد انتهى كل شيء.
كيف تحمي نفسك: PreparedStatement — درعك
\n\nالقاعدة الأولى في أمان تطبيقات جافا: لا تقم أبدًا بدمج بيانات المستخدم مع استعلامات SQL. استخدم الاستعلامات المُعلمة عبر PreparedStatement.
هذا هو الشكل الذي يجب أن يبدو عليه الكود الآمن:
\n\nString userInput = request.getParameter("username");\nString query = "SELECT * FROM users WHERE username = ?";\nPreparedStatement pstmt = connection.prepareStatement(query);\npstmt.setString(1, userInput);\nResultSet rs = pstmt.executeQuery();\n\nماذا حدث هنا؟ تتلقى قاعدة البيانات الاستعلام والبيانات بشكل منفصل. حتى إذا كان userInput يحتوي على كود SQL ضار، فسيتم التعامل معه كسلسلة نصية عادية، وليس كأمر. يقوم PreparedStatement تلقائيًا بترميز الأحرف الخاصة. هذا هو خط الدفاع الأول والأهم.
إجراءات إضافية: الإجراءات المخزنة والتحقق من الصحة
\n\nبالإضافة إلى PreparedStatement، يُنصح باستخدام:
- \n
- الإجراءات المخزنة — استدعاء الإجراء عبر
CallableStatementيفصل أيضًا الكود عن البيانات. \n - القوائم البيضاء — إذا كنت بحاجة إلى إدراج اسم جدول أو عمود (وهو أمر نادر)، فلا تأخذه أبدًا من إدخال المستخدم. استخدم enum أو ثوابت محددة بدقة. \n
- التحقق من صحة الإدخال — تحقق من أن البريد الإلكتروني يشبه البريد الإلكتروني بالفعل، وأن الرقم هو رقم. لكن تذكر: التحقق من الصحة هو طبقة إضافية، وليس بديلاً عن
PreparedStatement. \n
مثال للعمل الآمن مع أسماء الجداول الديناميكية:
\n\npublic enum TableName {\n USERS, PRODUCTS, ORDERS\n}\n\npublic void queryTable(TableName table) {\n String que