تحميل الرموز البرمجية الديناميكية

فئة OWASP: MASVS-CODE: جودة الرموز البرمجية

نظرة عامة

يؤدي تحميل الرمز البرمجي ديناميكيًا إلى تطبيق يتضمن مستوى مخاطر يجب التخفيف منه. يمكن للمهاجمين التلاعب بالرمز أو استبداله بهدف الوصول إلى البيانات الحساسة أو تنفيذ إجراءات ضارة.

إنّ العديد من أشكال تحميل الرموز البرمجية الديناميكية، خاصةً تلك التي تستخدم مصادر عن بُعد، تنتهك سياسات Google Play وقد تؤدي إلى تعليق تطبيقك من Google Play.

التأثير

إذا تمكّن المهاجمون من الوصول إلى الرمز الذي سيتم تحميله في التطبيق، يمكنهم تعديله لتحقيق أهدافهم. وقد يؤدي ذلك إلى عمليات استخراج البيانات واستغلال تنفيذ الرموز البرمجية. حتى إذا لم يتمكّن المهاجمون من تعديل الرمز البرمجي لتنفيذ إجراءات عشوائية من اختيارهم، يبقى من الممكن أن تتمكّن المهاجمون من إفساد الرمز البرمجي أو إزالته وبالتالي التأثير في مدى توفّر التطبيق.

إجراءات التخفيف

تجنُّب استخدام ميزة تحميل الرموز البرمجية الديناميكية

تجنَّب تحميل الرموز الديناميكية ما لم تكن هناك حاجة مرتبطة بالنشاط التجاري. يجب تفضيل تضمين جميع الوظائف في التطبيق مباشرةً كلما أمكن ذلك.

استخدام مصادر موثوقة

يجب تخزين الرمز الذي سيتم تحميله في التطبيق في مواقع موثوق بها. في ما يتعلّق بمساحة التخزين على الجهاز، فإنّ مساحة التخزين الداخلية للتطبيق أو مساحة التخزين المقيّدة (لنظام التشغيل Android 10 والإصدارات الأحدث) هما المكانان المُقترَحان. تتّخذ هذه المواقع الجغرافية إجراءات لتجنُّب الوصول المباشر من التطبيقات والمستخدمين الآخرين.

عند تحميل الرمز من مواقع عن بُعد، مثل عناوين 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!");
        }
    }
}

توقيع الرمز

هناك خيار آخر لضمان سلامة البيانات، وهو توقيع الرمز و التحقّق من توقيعه قبل تحميله. تتميز هذه الطريقة أيضًا بضمان سلامة رمز التجزئة، وليس الرمز نفسه فقط، ما يوفر حماية إضافية لمنع التلاعب.

على الرغم من أنّ توقيع الرموز البرمجية يقدّم طبقات أمان إضافية، من المهم أخذ في الاعتبار أنّها عملية أكثر تعقيدًا قد تتطلّب جهدًا وموارد إضافية لتنفيذها بنجاح.

يمكن العثور على بعض أمثلة على توقيع الرموز البرمجية في قسم "الموارد" ضمن هذا المستند.

المراجع