يعد الأمان أحد أهم المقاييس في تطبيقات الخادم. هذا نوع من المتطلبات غير الوظيفية
. يتضمن الأمن العديد من المكونات. وبطبيعة الحال، قد يستغرق الأمر أكثر من مقالة واحدة لتغطية جميع المبادئ الأمنية المعروفة والتدابير الأمنية بشكل كامل، لذلك سنتناول أهمها. يمكن لأي شخص على دراية جيدة بهذا الموضوع إعداد جميع العمليات ذات الصلة، وتجنب إنشاء ثغرات أمنية جديدة، وسيكون ذلك ضروريًا في أي فريق. بالطبع، لا يجب أن تعتقد أن تطبيقك سيكون آمنًا بنسبة 100% إذا اتبعت هذه الممارسات. لا! ولكن سيكون بالتأكيد أكثر أمنا معهم. دعنا نذهب.
1. توفير الأمان على مستوى لغة جافا
أولًا، يبدأ الأمان في Java على مستوى إمكانيات اللغة. ماذا سنفعل إذا لم تكن هناك معدّلات الوصول؟ لن يكون هناك شيء سوى الفوضى. تساعدنا لغة البرمجة في كتابة تعليمات برمجية آمنة كما أنها تستفيد من العديد من ميزات الأمان الضمنية:- كتابة قوية. Java هي لغة مكتوبة بشكل ثابت. وهذا يجعل من الممكن اكتشاف الأخطاء المتعلقة بالنوع في وقت التشغيل.
- معدّلات الوصول. يتيح لنا ذلك تخصيص الوصول إلى الفئات والأساليب والحقول حسب الحاجة.
- إدارة الذاكرة التلقائية. ولهذا السبب، يمتلك مطورو Java أداة تجميع البيانات المهملة التي تحررنا من الاضطرار إلى تكوين كل شيء يدويًا. نعم، تنشأ المشاكل في بعض الأحيان.
- التحقق من الرمز الثانوي : يتم تجميع Java في الرمز الثانوي، والذي يتم التحقق منه بواسطة وقت التشغيل قبل تنفيذه.
- تجنب إجراء تسلسل للفئات الحساسة للأمان. يكشف التسلسل عن واجهة الفئة في الملف المتسلسل، ناهيك عن البيانات التي تم تسلسلها.
- حاول تجنب الفئات القابلة للتغيير للبيانات. وهذا يوفر كافة فوائد الفئات غير القابلة للتغيير (على سبيل المثال، سلامة الخيط). إذا كان لديك كائن قابل للتغيير، فقد يؤدي ذلك إلى سلوك غير متوقع.
- عمل نسخ من الكائنات القابلة للتغيير التي تم إرجاعها. إذا قامت إحدى الطرق بإرجاع مرجع إلى كائن داخلي قابل للتغيير، فيمكن لرمز العميل تغيير الحالة الداخلية للكائن.
- وما إلى ذلك وهلم جرا…
2. القضاء على نقاط الضعف في حقن SQL
هذا هو نوع خاص من الضعف. إنها مميزة لأنها واحدة من أشهر نقاط الضعف وأكثرها شيوعًا. إذا لم تكن مهتمًا أبدًا بأمن الكمبيوتر، فلن تعرف عنه. ما هو حقن SQL؟ هذا هجوم على قاعدة بيانات يتضمن إدخال تعليمات برمجية SQL إضافية في مكان غير متوقع. لنفترض أن لدينا طريقة تقبل نوعًا ما من المعلمات للاستعلام عن قاعدة البيانات. على سبيل المثال، اسم المستخدم. سيبدو الرمز الضعيف كما يلي:// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
// Connect to the database
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
// Compose a SQL database query with our firstName
String query = "SELECT * FROM USERS WHERE firstName = " + firstName;
// Execute the query
Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery(query);
// Use mapToUsers to convert the ResultSet into a collection of users.
return mapToUsers(result);
}
private List mapToUsers(ResultSet resultSet) {
// Converts to a collection of users
}
في هذا المثال، يتم إعداد استعلام SQL مسبقًا في سطر منفصل. إذن ما هي المشكلة، أليس كذلك؟ ربما المشكلة هي أنه سيكون من الأفضل استخدام String.format ؟ لا؟ حسنا، ماذا بعد ذلك؟ دعونا نضع أنفسنا في مكان المختبر ونفكر فيما يمكن اعتباره قيمة الاسم الأول . على سبيل المثال:
- يمكننا تمرير ما هو متوقع - اسم المستخدم. ثم ستعيد قاعدة البيانات كافة المستخدمين بهذا الاسم.
- يمكننا تمرير سلسلة فارغة. ثم سيتم إرجاع كافة المستخدمين.
- ولكن يمكننا أيضًا تمرير ما يلي: "'; DROP TABLE USERS;". وهنا لدينا الآن مشاكل كبيرة. سيؤدي هذا الاستعلام إلى حذف جدول من قاعدة البيانات. جنبا إلى جنب مع كافة البيانات. كله.
// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
// Connect to the database
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
// Create a parameterized query.
String query = "SELECT * FROM USERS WHERE firstName = ?";
// Create a prepared statement with the parameterized query
PreparedStatement statement = connection.prepareStatement(query);
// Pass the parameter's value
statement.setString(1, firstName);
// Execute the query
ResultSet result = statement.executeQuery(query);
// Use mapToUsers to convert the ResultSet into a collection of users.
return mapToUsers(result);
}
private List mapToUsers(ResultSet resultSet) {
// Converts to a collection of users
}
بهذه الطريقة يتم تجنب الثغرة الأمنية. بالنسبة لأولئك الذين يريدون التعمق أكثر في هذه المقالة، إليك مثال رائع
. كيف تعرف متى تفهم هذه الثغرة الأمنية؟ إذا فهمت النكتة في القصة المصورة أدناه، فمن المحتمل أن يكون لديك فهم واضح لما تدور حوله هذه الثغرة الأمنية:D
3. مسح التبعيات وإبقائها محدثة
ماذا يعني ذالك؟ إذا كنت لا تعرف ما هي التبعية، سأشرح لك. التبعية هي أرشيف JAR يحتوي على رمز متصل بمشروع يستخدم أنظمة البناء التلقائية (Maven، Gradle، Ant) من أجل إعادة استخدام حل شخص آخر. على سبيل المثال، Project Lombok ، الذي يُنشئ حروفًا ومحددات وما إلى ذلك لنا في وقت التشغيل. يمكن أن تحتوي التطبيقات الكبيرة على الكثير والكثير من التبعيات. بعضها متعد (أي أن كل تبعية قد يكون لها تبعياتها الخاصة، وهكذا). ونتيجة لذلك، يولي المهاجمون اهتمامًا متزايدًا بالتبعيات مفتوحة المصدر، حيث يتم استخدامها بانتظام ويمكن أن يواجه العديد من العملاء مشكلات بسببها. من المهم التأكد من عدم وجود ثغرات أمنية معروفة في شجرة التبعية بأكملها (نعم، تبدو كشجرة). هناك عدة طرق للقيام بذلك.استخدم Snyk لمراقبة التبعية
يتحقق Snyk من جميع تبعيات المشروع ويحدد نقاط الضعف المعروفة. يمكنك التسجيل في Snyk واستيراد مشاريعك عبر GitHub. أيضًا، كما ترون من الصورة أعلاه، إذا تم إصلاح الثغرة الأمنية في إصدار أحدث، فسوف تقدم Snyk الإصلاح وتنشئ طلب سحب. يمكنك استخدامه مجانًا للمشاريع مفتوحة المصدر. يتم فحص المشاريع على فترات منتظمة، على سبيل المثال مرة واحدة في الأسبوع، مرة واحدة في الشهر. لقد قمت بالتسجيل وأضفت جميع مستودعاتي العامة إلى فحص Snyk (لا يوجد شيء خطير في هذا، لأنها متاحة للجميع بالفعل). ثم أظهر Snyk نتيجة الفحص: وبعد فترة، أعد Snyk-bot عدة طلبات سحب في المشاريع التي تحتاج إلى تحديث التبعيات: وأيضًا: هذه أداة رائعة للعثور على الثغرات الأمنية ومراقبة التحديثات للإصدارات الجديدة.استخدم GitHub Security Lab
يمكن لأي شخص يعمل على GitHub الاستفادة من أدواته المدمجة. يمكنك قراءة المزيد حول هذا النهج في منشور مدونتهم بعنوان الإعلان عن GitHub Security Lab . هذه الأداة، بالطبع، أبسط من Snyk، لكن بالتأكيد لا ينبغي عليك إهمالها. علاوة على ذلك، فإن عدد الثغرات الأمنية المعروفة سوف ينمو باستمرار، لذلك سيستمر كل من Snyk وGitHub Security Lab في التوسع والتحسين.تمكين سوناتايب DepShield
إذا كنت تستخدم GitHub لتخزين مستودعاتك، فيمكنك إضافة Sonatype DepShield، أحد التطبيقات الموجودة في MarketPlace، إلى مشاريعك. ويمكن استخدامه أيضًا لفحص المشاريع بحثًا عن التبعيات. علاوة على ذلك، إذا وجد شيئًا ما، فسيتم إنشاء مشكلة GitHub مع الوصف المناسب كما هو موضح أدناه:4. تعامل مع البيانات السرية بعناية
قد نستخدم بدلاً من ذلك عبارة "البيانات الحساسة". يمكن أن يؤدي تسريب المعلومات الشخصية للعميل وأرقام بطاقات الائتمان وغيرها من المعلومات الحساسة إلى ضرر لا يمكن إصلاحه. بادئ ذي بدء، ألق نظرة فاحصة على تصميم التطبيق الخاص بك وحدد ما إذا كنت تحتاج حقًا إلى هذه البيانات أو تلك. ربما لا تحتاج فعليًا إلى بعض البيانات الموجودة لديك — البيانات التي تمت إضافتها لمستقبل لم يأت ومن غير المرجح أن يأتي. بالإضافة إلى ذلك، قد تقوم بتسريب مثل هذه البيانات عن غير قصد من خلال التسجيل. إحدى الطرق السهلة لمنع دخول البيانات الحساسة إلى سجلاتك هي تنظيف أساليب toString() لكيانات المجال (مثل المستخدم والطالب والمعلم وما إلى ذلك). سيمنعك هذا من إخراج الحقول السرية عن طريق الخطأ. إذا كنت تستخدم Lombok لإنشاء طريقة toString() ، فيمكنك استخدام التعليق التوضيحي @ToString.Exclude لمنع استخدام الحقل في إخراج طريقة toString() . كن حذرًا جدًا أيضًا عند إرسال البيانات إلى العالم الخارجي. لنفترض أن لدينا نقطة نهاية HTTP تعرض أسماء جميع المستخدمين. ليست هناك حاجة لإظهار المعرف الداخلي الفريد للمستخدم. لماذا؟ لأنه يمكن للمهاجم استخدامه للحصول على معلومات أخرى أكثر حساسية حول المستخدم. على سبيل المثال، إذا كنت تستخدم Jackson لإجراء تسلسل/إلغاء تسلسل POJO إلى/من JSON ، فيمكنك استخدام التعليقات التوضيحية @JsonIgnore و @JsonIgnoreProperties لمنع التسلسل/إلغاء التسلسل لحقول معينة. بشكل عام، تحتاج إلى استخدام فئات POJO مختلفة في أماكن مختلفة. ماذا يعني ذالك؟- عند العمل مع قاعدة بيانات، استخدم نوعًا واحدًا من POJO (كيان).
- عند العمل باستخدام منطق الأعمال، قم بتحويل الكيان إلى نموذج.
- عند العمل مع العالم الخارجي وإرسال طلبات HTTP، استخدم كيانات مختلفة (DTOs).
استخدم خوارزميات التشفير والتجزئة القوية
يجب أن يتم تخزين البيانات السرية للعملاء بشكل آمن. للقيام بذلك، نحن بحاجة إلى استخدام التشفير. اعتمادًا على المهمة، عليك أن تقرر نوع التشفير الذي ستستخدمه. بالإضافة إلى ذلك، يستغرق التشفير الأقوى وقتًا أطول، لذا عليك مرة أخرى التفكير في مقدار الحاجة إليه التي تبرر الوقت الذي تقضيه فيه. بالطبع، يمكنك كتابة خوارزمية التشفير بنفسك. ولكن هذا غير ضروري. يمكنك استخدام الحلول الموجودة في هذا المجال. على سبيل المثال جوجل تينك :<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
<groupid>com.google.crypto.tink</groupid>
<artifactid>tink</artifactid>
<version>1.3.0</version>
</dependency>
دعونا نرى ما يجب فعله، باستخدام هذا المثال الذي يتضمن التشفير وفك التشفير:
private static void encryptDecryptExample() {
AeadConfig.register();
KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
String plaintext = "Elvis lives!";
String aad = "Buddy Holly";
Aead aead = handle.getPrimitive(Aead.class);
byte[] encrypted = aead.encrypt(plaintext.getBytes(), aad.getBytes());
String encryptedString = Base64.getEncoder().encodeToString(encrypted);
System.out.println(encryptedString);
byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encrypted), aad.getBytes());
System.out.println(new String(decrypted));
}
تشفير كلمات المرور
لهذه المهمة، من الأكثر أمانًا استخدام التشفير غير المتماثل. لماذا؟ لأن التطبيق لا يحتاج حقًا إلى فك تشفير كلمات المرور. هذا هو النهج القياسي. في الواقع، عندما يقوم المستخدم بإدخال كلمة مرور، يقوم النظام بتشفيرها ومقارنتها بما هو موجود في مخزن كلمات المرور. يتم تنفيذ نفس عملية التشفير، لذلك يمكننا أن نتوقع أنهما سيتطابقان، إذا تم إدخال كلمة المرور الصحيحة، بالطبع :) BCrypt وSCrypt مناسبان هنا. كلاهما دالتان أحادية الاتجاه (تجزئات التشفير) مع خوارزميات معقدة حسابيًا وتستغرق وقتًا طويلاً. هذا هو بالضبط ما نحتاجه، حيث أن الحسابات المباشرة سوف تستغرق إلى الأبد (حسنًا، وقت طويل جدًا). يدعم Spring Security مجموعة كاملة من الخوارزميات. يمكننا استخدام SCryptPasswordEncoder و BCryptPasswordEncoder . وما يعتبر حاليًا خوارزمية تشفير قوية قد يعتبر ضعيفًا في العام المقبل. ونتيجة لذلك، نستنتج أنه يجب علينا التحقق بانتظام من الخوارزميات التي نستخدمها، وإذا لزم الأمر، تحديث المكتبات التي تحتوي على خوارزميات التشفير.بدلا من الاستنتاج
تحدثنا اليوم عن الأمن، وبطبيعة الحال، تم ترك الكثير من الأشياء وراء الكواليس. لقد فتحت للتو الباب أمام عالم جديد لك، عالم له حياة خاصة به. الأمن مثل السياسة: إذا لم تشغل نفسك بالسياسة، فسوف تشغل السياسة بك. أقترح تقليديًا أن تتابعني على حساب GitHub . هناك أنشر إبداعاتي التي تتضمن تقنيات مختلفة أدرسها وأطبقها في العمل.روابط مفيدة
- Guru99: البرنامج التعليمي لحقن SQL
- أوراكل: مركز موارد أمن جافا
- Oracle: إرشادات الترميز الآمن لـ Java SE
- Baeldung: أساسيات أمن جافا
- متوسط: 10 نصائح لتعزيز أمان Java لديك
- Snyk: 10 أفضل ممارسات أمان Java
- GitHub: الإعلان عن مختبر GitHub Security Lab: تأمين الكود العالمي معًا
GO TO FULL VERSION