動的コード読み込み

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

コードに署名する

データの整合性を確保するもう 1 つの方法は、コードに署名し、読み込む前に署名を検証することです。この方法には、コード自体だけでなく、ハッシュコードの整合性も確保できるというメリットがあります。これにより、改ざん防止の保護が強化されます。

コード署名によりセキュリティ レイヤが追加されますが、実装を成功させるには追加の労力とリソースが必要になる可能性がある、より複雑なプロセスであることを考慮することが重要です。

コード署名の例については、このドキュメントのリソース セクションをご覧ください。

リソース