Carregamento dinâmico de código

Categoria do OWASP: MASVS-CODE - Qualidade do código (link em inglês)

Visão geral

O carregamento dinâmico de código em um aplicativo introduz um nível de risco que precisa ser mitigado. Os invasores podem adulterar ou substituir o código para acessar dados sensíveis ou executar ações nocivas.

Muitas formas de carregamento de código dinâmico, especialmente aquelas que usam fontes remotas, violam as políticas do Google Play e podem levar à suspensão do app no Google Play.

Impacto

Se os invasores conseguirem acesso ao código que será carregado no aplicativo, eles poderão modificá-lo para atender aos objetivos deles. Isso pode levar a extração de dados e exploits de execução de código. Mesmo que os invasores não possam modificar o código para realizar ações arbitrárias, ainda é possível que eles corrompam ou removam o código e, assim, afetem a disponibilidade do aplicativo.

Mitigações

Evite usar o carregamento de código dinâmico

A menos que haja uma necessidade comercial, evite o carregamento de código dinâmico. Sempre que possível, inclua todas as funcionalidades diretamente no aplicativo.

Usar fontes confiáveis

O código que será carregado no aplicativo precisa ser armazenado em locais confiáveis. Em relação ao armazenamento local, o armazenamento interno do aplicativo ou o armazenamento com escopo (para o Android 10 e versões mais recentes) são os lugares recomendados. Esses locais têm medidas para evitar o acesso direto de outros aplicativos e usuários.

Ao carregar código de locais remotos, como URLs, evite usar terceiros sempre que possível e armazene o código na sua própria infraestrutura, seguindo as práticas de segurança recomendadas. Se você precisar carregar um código de terceiros, verifique se o provedor é confiável.

Realizar verificações de integridade

As verificações de integridade são recomendadas para garantir que o código não tenha sido adulterado. Essas verificações precisam ser realizadas antes de carregar o código no aplicativo.

Ao carregar recursos remotos, a integridade do subrecurso pode ser usada para validar a integridade dos recursos acessados.

Ao carregar recursos do armazenamento externo, use verificações de integridade para verificar se nenhum outro aplicativo adulterou esses dados ou o código. Os hashes dos arquivos precisam ser armazenados de forma segura, de preferência criptografados e no armazenamento interno.

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

Assinar o código

Outra opção para garantir a integridade dos dados é assinar o código e verificar a assinatura antes de carregá-lo. Esse método tem a vantagem de também garantir a integridade do código hash, não apenas o código em si, o que oferece uma proteção anti-adulteração adicional.

Embora a assinatura de código ofereça camadas de segurança adicionais, é importante considerar que esse é um processo mais complexo que pode exigir mais esforço e recursos para ser implementado.

Alguns exemplos de assinatura de código podem ser encontrados na seção "Recursos" deste documento.

Recursos