Categoria OWASP: MASVS-CODE: Code Quality
Panoramica
Il caricamento dinamico del codice in un'applicazione introduce un livello di rischio che deve essere mitigato. Gli autori degli attacchi potrebbero potenzialmente manomettere o sostituire il codice per accedere a dati sensibili o eseguire azioni dannose.
Molte forme di caricamento dinamico del codice, in particolare quelle che utilizzano origini remote, violano le norme di Google Play e potrebbero comportare la sospensione della tua app da Google Play.
Impatto
Se gli autori di attacchi riescono ad accedere al codice che verrà caricato nell'applicazione, potrebbero modificarlo per supportare i loro obiettivi. Ciò potrebbe portare all'esfiltrazione di dati e a exploit di esecuzione del codice. Anche se gli autori degli attacchi non possono modificare il codice per eseguire azioni arbitrarie a loro scelta, è comunque possibile che possano danneggiare o rimuovere il codice e quindi influire sulla disponibilità dell' applicazione.
Mitigazioni
Evitare l'utilizzo del caricamento dinamico del codice
A meno che non ci sia un'esigenza aziendale, evita il caricamento dinamico del codice. Ti consigliamo di includere tutte le funzionalità direttamente nell'applicazione, ove possibile.
Utilizzare fonti attendibili
Il codice che verrà caricato nell'applicazione deve essere archiviato in posizioni attendibili. Per quanto riguarda lo spazio di archiviazione locale, la memoria interna dell'applicazione o l'archiviazione mirata (per Android 10 e versioni successive) sono i luoghi consigliati. Queste posizioni hanno misure per evitare l'accesso diretto da altre applicazioni e utenti.
Quando carichi codice da posizioni remote come gli URL, evita di utilizzare terze parti quando possibile e archivia il codice nella tua infrastruttura, seguendo le best practice di sicurezza. Se devi caricare codice di terze parti, assicurati che il fornitore sia attendibile.
Eseguire controlli di integrità
Per assicurarsi che il codice non sia stato manomesso, si consigliano controlli di integrità. Questi controlli devono essere eseguiti prima di caricare il codice nell'applicazione.
Quando carichi risorse remote, puoi utilizzare l'integrità delle subrisorse per convalidare l'integrità delle risorse a cui accedi.
Quando carichi risorse dalla memoria esterna, utilizza i controlli di integrità per verificare che nessun'altra applicazione abbia manomesso questi dati o questo codice. Gli hash dei file devono essere archiviati in modo sicuro, preferibilmente criptati e nella memoria interna.
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!");
}
}
}
Firma il codice
Un'altra opzione per garantire l'integrità dei dati è firmare il codice e verificare la firma prima di caricarlo. Questo metodo ha il vantaggio di garantire anche l'integrità del codice hash, non solo del codice stesso, il che fornisce una protezione aggiuntiva antimanomissione.
Sebbene la firma del codice fornisca ulteriori livelli di sicurezza, è importante tenere presente che si tratta di un processo più complesso che potrebbe richiedere ulteriori sforzi e risorse per essere implementato correttamente.
Alcuni esempi di firma del codice sono disponibili nella sezione Risorse di questo documento.
Risorse
- Integrità delle risorse secondarie
- Firma digitale dei dati
- Firma del codice
- Dati sensibili archiviati in un'unità di archiviazione esterna