Chargement de code dynamique

Catégorie OWASP : MASVS-CODE : qualité du code

Présentation

Le chargement dynamique de code dans une application introduit un niveau de risque qui doit être atténué. Les pirates informatiques peuvent potentiellement falsifier ou remplacer le code pour accéder à des données sensibles ou exécuter des actions dangereuses.

De nombreuses formes de chargement de code dynamique, en particulier celles qui utilisent des sources distantes, enfreignent les règles de Google Play et peuvent entraîner la suspension de votre application sur Google Play.

Impact

Si des pirates informatiques parviennent à accéder au code qui sera chargé dans l'application, ils peuvent le modifier pour atteindre leurs objectifs, ce qui peut entraîner l'exfiltration de données et le piratage d'exécution du code. Même si les pirates informatiques ne peuvent pas modifier le code pour effectuer des actions arbitraires de leur choix, il est toujours possible qu'ils puissent corrompre ou supprimer le code et ainsi affecter la disponibilité de l'application.

Stratégies d'atténuation

Éviter d'utiliser le chargement de code dynamique

Sauf si c'est nécessaire d'un point de vue commercial, évitez le chargement de code dynamique. Dans la mesure du possible, vous devez inclure toutes les fonctionnalités directement dans l'application.

Utiliser des sources fiables

Le code qui sera chargé dans l'application doit être stocké dans des emplacements approuvés. Pour le stockage local, l'espace de stockage interne de l'application ou l'espace de stockage cloisonné (pour Android 10 et versions ultérieures) sont les emplacements recommandés. Ces emplacements sont dotés de mesures pour éviter l'accès direct d'autres applications et utilisateurs.

Lorsque vous chargez du code à partir d'emplacements distants, tels que des URL, évitez autant que possible de faire appel à des tiers et stockez le code dans votre propre infrastructure, en respectant les bonnes pratiques de sécurité. Si vous devez charger du code tiers, assurez-vous que le fournisseur est fiable.

Effectuer des vérifications de l'intégrité

Les vérifications d'intégrité sont recommandées pour s'assurer que le code n'a pas été altéré. Ces vérifications doivent être effectuées avant de charger le code dans l'application.

Lors du chargement des ressources distantes, l'intégrité des sous-ressources peut être utilisée afin de valider l'intégrité des ressources consultées.

Lorsque vous chargez des ressources à partir du stockage externe, utilisez des vérifications d'intégrité pour vérifier qu'aucune autre application n'a falsifié ces données ou ce code. Les hachages des fichiers doivent être stockés de manière sécurisée, de préférence chiffrées et dans la mémoire de stockage interne.

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

Signez le code

Une autre option pour garantir l'intégrité des données consiste à signer le code et à vérifier sa signature avant de le charger. Cette méthode présente l'avantage de garantir également l'intégrité du code de hachage, et non seulement du code lui-même, ce qui offre une protection anti-tamponnage supplémentaire.

Bien que la signature du code fournisse des couches de sécurité supplémentaires, il est important de tenir compte du fait qu'il s'agit d'un processus plus complexe qui peut nécessiter des efforts et des ressources supplémentaires pour être correctement mis en œuvre.

Vous trouverez des exemples de signature de code dans la section "Ressources" de ce document.

Ressources