Kategoria OWASP: MASVS-CODE: jakość kodu
Omówienie
Ładowanie kodu dynamicznie w aplikacji wiąże się z pewnym poziomem ryzyka, który należy ograniczyć. Osoby atakujące mogą zmodyfikować lub zastąpić kod, aby uzyskać dostęp do danych wrażliwych lub wykonać szkodliwe działania.
Różne formy dynamicznego wczytywania kodu, zwłaszcza te korzystające ze źródeł zdalnych, naruszają zasady Google Play i mogą spowodować zawieszenie aplikacji w Google Play.
Wpływ
Jeśli haker uzyska dostęp do kodu, który zostanie wczytany do aplikacji, może go zmodyfikować zgodnie z wyznaczonymi celami. Może to prowadzić do wydobycia danych i wykorzystania luk w zabezpieczeniach do wykonania kodu. Nawet jeśli atakujący nie mogą zmodyfikować kodu, aby wykonać dowolne działania, nadal mogą uszkodzić lub usunąć kod, wpływając w ten sposób na dostępność aplikacji.
Łagodzenie
Unikaj dynamicznego wczytywania kodu
Unikaj ładowania kodu dynamicznego, chyba że jest to konieczne ze względów biznesowych. W miarę możliwości zalecamy dodanie wszystkich funkcji bezpośrednio do aplikacji.
Korzystanie z zaufanych źródeł
Kod, który ma być wczytany do aplikacji, powinien być przechowywany w zaufanych lokalizacjach. Jeśli chodzi o pamięć lokalną, zalecamy używanie pamięci wewnętrznej aplikacji lub ograniczonej pamięci (w przypadku Androida 10 lub nowszego). Te lokalizacje mają środki zapobiegające bezpośredniemu dostępowi z innych aplikacji i użytkowników.
Podczas wczytywania kodu z odległych lokalizacji, takich jak adresy URL, unikaj, jeśli to możliwe, korzystania z usług zewnętrznych i przechowuj kod w ramach własnej infrastruktury zgodnie z zalecanymi najlepszymi praktykami dotyczącymi bezpieczeństwa. Jeśli musisz wczytać kod zewnętrzny, upewnij się, że dostawca jest zaufany.
Wykonywanie testów integralności
Zalecamy sprawdzanie integralności, aby mieć pewność, że kod nie został zmodyfikowany. Te kontrole należy przeprowadzić przed załadowaniem kodu do aplikacji.
Podczas wczytywania zasobów zdalnych można użyć integralności zasobów podrzędnych, aby sprawdzić integralność zasobów, do których uzyskuje się dostęp.
Podczas wczytywania zasobów z pamięci zewnętrznej użyj testów integralności, aby sprawdzić, czy żadna inna aplikacja nie zmodyfikowała tych danych ani kodu. Wartości skrótów plików powinny być przechowywane w bezpieczny sposób, najlepiej w zaszyfrowanej pamięci wewnętrznej.
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!");
}
}
}
Podpisz kod
Inną opcją zapewniającą integralność danych jest podpisanie kodu i sprawdzenie jego podpisu przed załadowaniem. Zaletą tej metody jest to, że zapewnia integralność kodu szyfrującego, a nie tylko samego kodu, co zapewnia dodatkową ochronę przed modyfikacją.
Chociaż podpisywanie kodu zapewnia dodatkowe warstwy zabezpieczeń, należy pamiętać, że jest to bardziej złożony proces, który może wymagać dodatkowych nakładów pracy i zasobów.
Przykłady podpisywania kodu znajdziesz w sekcji Zasoby tego dokumentu.
Materiały
- Uczciwość podelementów
- Podpisywanie danych cyfrowo
- Podpisywanie kodu
- Dane wrażliwe przechowywane na zewnętrznym urządzeniu do przechowywania danych