Categoria OWASP: MASVS-CODE: Qualità del codice
Panoramica
Il caricamento dinamico del codice in un'applicazione introduce un livello di rischio che deve essere mitigato. Gli utenti malintenzionati potrebbero potenzialmente manomettere o sostituire il codice per accedere a dati sensibili o eseguire azioni dannose.
Molte forme di caricamento di codice dinamico, 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 utenti malintenzionati riescono ad accedere al codice che verrà caricato nell'applicazione, potrebbero modificarlo per supportare i propri obiettivi. Ciò potrebbe portare a esfiltrazioni di dati ed exploit di esecuzione di codice. Anche se i malintenzionati non riescono a modificare il codice per eseguire azioni arbitrarie di loro scelta, è comunque possibile che possano danneggiare o rimuovere il codice e quindi influire sulla disponibilità dell'applicazione.
Mitigazioni
Evita di utilizzare il caricamento di codice dinamico
A meno che non sia necessario per l'attività, evita il caricamento di codice dinamico. Se possibile, ti consigliamo di includere tutte le funzionalità direttamente nell'applicazione.
Utilizzare fonti attendibili
Il codice che verrà caricato nell'applicazione deve essere archiviato in posizioni attendibili. Per quanto riguarda l'archiviazione locale, ti consigliamo di utilizzare la memoria interna dell'applicazione o l'archiviazione con ambito (per Android 10 e versioni successive). Queste posizioni hanno misure per evitare l'accesso diretto da altre applicazioni e altri utenti.
Quando carichi codice da posizioni remote come gli URL, evita di utilizzare terze parti se 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.
Esegui controlli di integrità
I controlli di integrità sono consigliati per assicurarsi che il codice non sia stato manomesso. Questi controlli devono essere eseguiti prima di caricare il codice nell'applicazione.
Quando si caricano risorse remote, è possibile utilizzare l'integrità delle sottorisorse per convalidare l'integrità delle risorse a cui si accede.
Quando carichi le risorse dalla memoria esterna, usa i controlli di integrità per verificare che nessun'altra applicazione abbia manomesso questi dati o codice. Gli hash dei file devono essere archiviati in modo sicuro, preferibilmente criptati e nello stoccaggio interno.
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 verificarne la firma prima di caricarlo. Questo metodo ha il vantaggio di garantire anche l'integrità del codice hash, non solo del codice stesso, che offre una protezione antimanomissione aggiuntiva.
Sebbene la firma del codice fornisca livelli di sicurezza aggiuntivi, è importante tenere conto del fatto che si tratta di un processo più complesso che potrebbe richiedere un impegno e risorse aggiuntivi per essere implementato correttamente.
Alcuni esempi di firma codice sono disponibili nella sezione Risorse di questo documento.
Risorse
- Integrità dei componenti secondari
- Firma digitale dei dati
- Firma codice
- Dati sensibili archiviati in un'archiviazione esterna