動的コード読み込み

OWASP カテゴリ: MASVS-CODE: コード品質

概要

コードをアプリケーションに動的に読み込むと、軽減する必要があるリスクレベルが生じます。攻撃者は、コードを改ざんまたは置き換えて、機密データにアクセスしたり、有害なアクションを実行したりする可能性があります。

動的コード読み込みの多くの形式(特にリモート ソースを使用するもの)は、Google Play ポリシーに違反しており、Google Play でのアプリの停止につながる可能性があります。

影響

攻撃者がアプリケーションに読み込まれるコードにアクセスできた場合、攻撃者は自分の目的を達成するためにコードを変更する可能性があります。これにより、データの漏洩やコード実行の悪用につながる可能性があります。攻撃者が任意のアクションを実行するためにコードを変更できない場合でも、コードを破損または削除してアプリケーションの可用性に影響する可能性があります。

リスクの軽減

動的コード読み込みを使用しないようにする

ビジネス上の必要性がない限り、動的コードの読み込みは避けてください。可能な限り、すべての機能をアプリに直接含めるようにしてください。

信頼できる情報源を使用する

アプリケーションに読み込まれるコードは、信頼できる場所に保存する必要があります。ローカル ストレージについては、アプリの内部ストレージまたは対象範囲別ストレージ(Android 10 以降)を使用することをおすすめします。これらの場所には、他のアプリケーションやユーザーからの直接アクセスを回避するための対策が講じられています。

URL などのリモート ロケーションからコードを読み込む場合は、可能であればサードパーティの使用を避け、セキュリティのベスト プラクティスに沿って独自のインフラストラクチャにコードを保存します。サードパーティのコードを読み込む必要がある場合は、プロバイダが信頼できるものであることを確認してください。

完全性チェックを実行する

コードが改ざんされていないことを確認するために、完全性チェックを実施することをおすすめします。これらのチェックは、コードをアプリケーションに読み込む前に行う必要があります。

リモート リソースを読み込むときに、サブリソースの整合性を使用して、アクセスされたリソースの整合性を検証できます。

外部ストレージからリソースを読み込むときは、整合性チェックを使用して、他のアプリがこのデータやコードを改ざんしていないことを確認します。ファイルのハッシュは、安全な方法で保存する必要があります。暗号化して内部ストレージに保存することをおすすめします。

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

コードに署名する

データの整合性を確保する別の方法として、コードに署名し、読み込む前に署名を検証することもできます。この方法の利点は、コード自体だけでなくハッシュコードの完全性も確保できるため、改ざん防止保護を強化できることです。

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

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

リソース