Dynamiczne wczytywanie kodu

Kategoria OWASP: MASVS-CODE: Jakość kodu

Przegląd

Dynamiczne ładowanie kodu do aplikacji wiąże się z poziomem ryzyka, który należy ograniczyć. Osoby atakujące mogą potencjalnie manipulować kodem lub go zastępować, aby uzyskać dostęp do danych wrażliwych lub wykonać szkodliwe działania.

Wiele form dynamicznego wczytywania kodu, zwłaszcza tych, które korzystają ze źródeł zdalnych, narusza zasady Google Play i może prowadzić do zawieszenia aplikacji w Google Play.

Wpływ

Jeśli atakującym uda się uzyskać dostęp do kodu, który zostanie załadowany do aplikacji, mogą go zmodyfikować, aby osiągnąć swoje cele. Może to prowadzić do wydobycia danych i wykorzystania luk w zabezpieczeniach do uruchomienia kodu. Nawet jeśli atakujący nie mogą modyfikować kodu, aby wykonywać dowolne działania, mogą go uszkodzić lub usunąć, a tym samym wpłynąć na dostępność aplikacji.

Środki ograniczające ryzyko

Unikaj dynamicznego wczytywania kodu

Jeśli nie zachodzi potrzeba biznesowa, unikaj dynamicznego wczytywania kodu. W miarę możliwości wszystkie funkcje powinny być wbudowane bezpośrednio w aplikację.

Korzystanie z zaufanych źródeł

Kod, który ma być wczytywany do aplikacji, powinien być przechowywany w zaufanych lokalizacjach. W przypadku pamięci lokalnej zalecamy korzystanie z pamięci wewnętrznej aplikacji lub pamięci o ograniczonym zakresie (w Androidzie 10 i nowszych wersjach). W tych lokalizacjach zastosowano środki zapobiegające bezpośredniemu dostępowi z innych aplikacji i od innych użytkowników.

Podczas wczytywania kodu z lokalizacji zdalnych, takich jak adresy URL, unikaj w miarę możliwości korzystania z usług innych firm i przechowuj kod we własnej infrastrukturze, postępując zgodnie z zasadami bezpieczeństwa. Jeśli musisz wczytać kod innej firmy, upewnij się, że dostawca jest zaufany.

Przeprowadzanie testów integralności

Zalecamy przeprowadzenie testów integralności, aby upewnić się, że kod nie został zmodyfikowany. Te kontrole należy przeprowadzić przed wczytaniem kodu do aplikacji.

Podczas ładowania zasobów zdalnych można użyć funkcji sprawdzania integralności zasobów podrzędnych, aby zweryfikować integralność zasobów, do których uzyskano dostęp.

Podczas wczytywania zasobów z pamięci zewnętrznej używaj kontroli integralności, aby sprawdzić, czy żadna inna aplikacja nie zmodyfikowała tych danych ani kodu. Wartości hash plików powinny być przechowywane w bezpieczny sposób, najlepiej w formie zaszyfrowanej i w pamięci wewnętrznej.

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!");
        }
    }
}

Podpisywanie kodu

Innym sposobem na zapewnienie integralności danych jest podpisanie kodu i zweryfikowanie podpisu przed jego załadowaniem. Zaletą tej metody jest to, że zapewnia ona integralność kodu skrótu, a nie tylko samego kodu, co stanowi dodatkową ochronę przed manipulacją.

Podpisywanie kodu zapewnia dodatkowe warstwy zabezpieczeń, ale należy pamiętać, że jest to bardziej złożony proces, który może wymagać dodatkowego wysiłku i zasobów.

Przykłady podpisywania kodu znajdziesz w sekcji Zasoby w tym dokumencie.

Zasoby