Categoría de OWASP: MASVS-CODE: Calidad de código
Descripción general
La carga dinámica de código en una aplicación introduce un nivel de riesgo que se debe mitigar. Los atacantes podrían manipular o sustituir el código para acceder a datos sensibles o ejecutar acciones dañinas.
Muchas formas de carga de código dinámico, en especial las que usan fuentes remotas, infrinjan las políticas de Google Play y pueden provocar la suspensión de tu app de Google Play.
Impacto
Si los atacantes logran obtener acceso al código que se cargará en la aplicación, podrían modificarlo para respaldar sus objetivos. Esto podría generar exploits de robo de datos y ejecución de código. Incluso si los atacantes no pueden modificar el código para realizar acciones arbitrarias de su elección, es posible que puedan dañar o quitar el código y, de esta manera, afectar la disponibilidad de la aplicación.
Mitigaciones
Evita usar la carga de código dinámico
A menos que haya una necesidad comercial, evita la carga dinámica de código. Siempre que sea posible, recomendamos incluir todas las funciones directamente en la aplicación.
Usa fuentes de confianza
El código que se cargará en la aplicación debe almacenarse en ubicaciones confiables. En cuanto al almacenamiento local, el almacenamiento interno de la aplicación o el almacenamiento específico (para Android 10 y versiones posteriores) son los lugares recomendados. Estas ubicaciones tienen medidas para evitar el acceso directo desde otras aplicaciones y usuarios.
Cuando cargues código desde ubicaciones remotas, como URLs, evita usar terceros cuando sea posible y almacena el código en tu propia infraestructura siguiendo las prácticas recomendadas de seguridad. Si necesitas cargar código de terceros, asegúrate de que el proveedor sea de confianza.
Realiza verificaciones de integridad
Se recomienda realizar verificaciones de integridad para garantizar que no se haya alterado el código. Estas verificaciones deben realizarse antes de cargar el código en la aplicación.
Cuando se cargan recursos remotos, se puede usar la integridad de los subrecursos para validar la integridad de los recursos a los que se accedió.
Cuando cargues recursos desde el almacenamiento externo, usa verificaciones de integridad para verificar que ninguna otra aplicación haya manipulado estos datos o código. Los valores hash de los archivos deben almacenarse de forma segura, preferiblemente encriptados y en el almacenamiento 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 el código
Otra opción para garantizar la integridad de los datos es firmar el código y verificar su firma antes de cargarlo. Este método tiene la ventaja de garantizar también la integridad del código hash, no solo el código en sí, lo que proporciona una protección adicional contra la manipulación.
Si bien la firma de código proporciona capas de seguridad adicionales, es importante tener en cuenta que es un proceso más complejo que puede requerir esfuerzo y recursos adicionales para implementarse de forma correcta.
Puedes encontrar algunos ejemplos de firma de código en la sección Recursos de este documento.
Recursos
- Integridad de subrecursos
- Firma digital de datos
- Firma de código
- Datos sensibles almacenados en el almacenamiento externo