فئة OWASP: MASVS-CODE: جودة الرمز
نظرة عامة
يؤدي تحميل الرموز البرمجية ديناميكيًا في تطبيق إلى مستوى من المخاطر يجب الحدّ منه. وقد يتلاعب المهاجمون بالرمز أو يستبدلونه بهدف الوصول إلى بيانات حساسة أو تنفيذ إجراءات ضارة.
إنّ العديد من أشكال تحميل الرموز البرمجية الديناميكية، خاصةً تلك التي تستخدم مصادر بعيدة، تنتهك سياسات Google Play وقد تؤدي إلى تعليق تطبيقك على Google Play.
التأثير
إذا تمكّن المهاجمون من الوصول إلى الرمز الذي سيتم تحميله في التطبيق، يمكنهم تعديله لتحقيق أهدافهم، ما قد يؤدي إلى استغلال الثغرات الأمنية في استخراج البيانات وتنفيذ الرموز البرمجية. حتى إذا لم يتمكّن المهاجمون من تعديل الرمز البرمجي لتنفيذ إجراءات عشوائية من اختيارهم، سيظل بإمكانهم إتلاف الرمز البرمجي أو إزالته، وبالتالي التأثير في مدى توفّر التطبيق.
إجراءات التخفيف
تجنُّب استخدام تحميل الرموز البرمجية الديناميكية
تجنَّب تحميل الرموز الديناميكية ما لم تكن هناك حاجة للمؤسسة. ننصحك بتضمين جميع الوظائف مباشرةً في التطبيق كلما أمكن ذلك.
استخدام مصادر موثوقة
يجب تخزين الرمز الذي سيتم تحميله في التطبيق في مواقع موثوق بها. في ما يتعلق بمساحة التخزين المحلية، ننصح باستخدام مساحة التخزين الداخلية للتطبيق أو مساحة التخزين المحصورة النطاق (في الإصدار 10 من نظام التشغيل Android والإصدارات الأحدث). تتضمّن هذه المواقع الجغرافية إجراءات لتجنُّب الوصول المباشر من التطبيقات والمستخدمين الآخرين.
عند تحميل الرمز من مواقع جغرافية بعيدة، مثل عناوين URL، تجنَّب استخدام جهات خارجية قدر الإمكان، وخزِّن الرمز في البنية الأساسية الخاصة بك، مع اتّباع أفضل ممارسات الأمان. إذا كنت بحاجة إلى تحميل رمز تابع لجهة خارجية، تأكَّد من أنّ مقدّم الخدمة موثوق به.
إجراء عمليات التحقّق من السلامة
يُنصح بإجراء عمليات التحقّق من التكامل للتأكّد من عدم التلاعب بالرمز. يجب إجراء عمليات التحقّق هذه قبل تحميل الرمز إلى التطبيق.
عند تحميل موارد بعيدة، يمكن استخدام ميزة "سلامة الموارد الفرعية" للتحقّق من سلامة الموارد التي يتم الوصول إليها.
عند تحميل الموارد من وحدة التخزين الخارجية، استخدِم عمليات التحقّق من السلامة للتأكّد من أنّ أي تطبيق آخر لم يتلاعب بهذه البيانات أو الرموز. يجب تخزين تجزئات الملفات بطريقة آمنة، ويُفضّل أن تكون مشفّرة وفي وحدة التخزين الداخلية.
Kotlin
package com.example.myapplication
import java.io.BufferedInputStream
import java.io.FileInputStream
import java.io.IOException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
object FileIntegrityChecker {
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun getIntegrityHash(filePath: String?): String {
val md = MessageDigest.getInstance("SHA-256") // You can choose other algorithms as needed
val buffer = ByteArray(8192)
var bytesRead: Int
BufferedInputStream(FileInputStream(filePath)).use { fis ->
while (fis.read(buffer).also { bytesRead = it } != -1) {
md.update(buffer, 0, bytesRead)
}
}
private fun bytesToHex(bytes: ByteArray): String {
val sb = StringBuilder(bytes.length * 2)
for (b in bytes) {
sb.append(String.format("%02x", b))
}
return sb.toString()
}
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun verifyIntegrity(filePath: String?, expectedHash: String): Boolean {
val actualHash = getIntegrityHash(filePath)
return actualHash == expectedHash
}
@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
val filePath = "/path/to/your/file"
val expectedHash = "your_expected_hash_value"
if (verifyIntegrity(filePath, expectedHash)) {
println("File integrity is valid!")
} else {
println("File integrity is compromised!")
}
}
}
Java
package com.example.myapplication;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileIntegrityChecker {
public static String getIntegrityHash(String filePath) throws IOException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256"); // You can choose other algorithms as needed
byte[] buffer = new byte[8192];
int bytesRead;
try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(filePath))) {
while ((bytesRead = fis.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
}
byte[] digest = md.digest();
return bytesToHex(digest);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static boolean verifyIntegrity(String filePath, String expectedHash) throws IOException, NoSuchAlgorithmException {
String actualHash = getIntegrityHash(filePath);
return actualHash.equals(expectedHash);
}
public static void main(String[] args) throws Exception {
String filePath = "/path/to/your/file";
String expectedHash = "your_expected_hash_value";
if (verifyIntegrity(filePath, expectedHash)) {
System.out.println("File integrity is valid!");
} else {
System.out.println("File integrity is compromised!");
}
}
}
توقيع الرمز
هناك خيار آخر لضمان سلامة البيانات، وهو توقيع الرمز والتحقّق من توقيعه قبل تحميله. يتميّز هذا الأسلوب أيضًا بضمان سلامة رمز التجزئة، وليس الرمز نفسه فقط، ما يوفّر حماية إضافية ضد التلاعب.
على الرغم من أنّ توقيع الرمز البرمجي يوفّر طبقات أمان إضافية، من المهم الأخذ في الاعتبار أنّه عملية أكثر تعقيدًا وقد تتطلّب جهدًا وموارد إضافية لتنفيذها بنجاح.
يمكن العثور على بعض الأمثلة على توقيع الرموز في قسم "المراجع" من هذا المستند.
الموارد
- Subresource Integrity
- توقيع البيانات رقميًا
- التوقيع بالرمز
- البيانات الحساسة المخزّنة في وحدة التخزين الخارجية