Категория OWASP: MASVS-STORAGE: Хранилище
Обзор
Приложения, ориентированные на Android 10 (API 29) или более ранние версии, не используют ограничение доступа к внешнему хранилищу . Это означает, что к любым данным, хранящимся на внешнем накопителе, может получить доступ любое другое приложение, имеющее разрешение READ_EXTERNAL_STORAGE .
Влияние
В приложениях, ориентированных на Android 10 (API 29) или более ранние версии, если конфиденциальные данные хранятся на внешнем накопителе, любое приложение на устройстве с разрешением READ_EXTERNAL_STORAGE может получить к ним доступ. Это позволяет вредоносным приложениям незаметно получать доступ к конфиденциальным файлам, постоянно или временно хранящимся на внешнем накопителе. Кроме того, поскольку к содержимому внешнего накопителя может получить доступ любое приложение в системе, любое вредоносное приложение, которое также имеет разрешение WRITE_EXTERNAL_STORAGE, может изменять файлы, хранящиеся на внешнем накопителе, например, для добавления вредоносных данных. Эти вредоносные данные, если их загрузить в приложение, могут быть предназначены для обмана пользователей или даже для выполнения кода.
Меры по смягчению последствий
Ограниченное хранилище (Android 10 и более поздние версии)
Android 10
Для приложений, ориентированных на Android 10, разработчики могут явно включить ограничение доступа к хранилищу. Этого можно добиться, установив флаг requestLegacyExternalStorage в значение false в файле AndroidManifest.xml . При ограниченном доступе к хранилищу приложения могут получать доступ только к файлам, которые они создали сами на внешнем хранилище, или к типам файлов, которые были сохранены с использованием API MediaStore, таким как аудио и видео. Это помогает защитить конфиденциальность и безопасность пользователей.
Android 11 и более поздние версии
Для приложений, ориентированных на Android 11 или более поздние версии, ОС принудительно использует хранилище с ограниченной областью видимости , то есть игнорирует флаг requestLegacyExternalStorage и автоматически защищает внешнее хранилище приложений от несанкционированного доступа.
Используйте внутреннюю память для хранения конфиденциальных данных.
Независимо от целевой версии Android, конфиденциальные данные приложения всегда должны храниться во внутренней памяти. Благодаря песочнице Android доступ к внутренней памяти автоматически ограничивается только приложением-владельцем, поэтому её можно считать безопасной, если устройство не рутировано.
Шифрование конфиденциальных данных
Если сценарии использования приложения требуют хранения конфиденциальных данных на внешнем носителе, эти данные следует зашифровать. Рекомендуется использовать надежный алгоритм шифрования, а для безопасного хранения ключа — Android KeyStore .
В целом, шифрование всех конфиденциальных данных является рекомендуемой мерой безопасности, независимо от места их хранения.
Важно отметить, что полное шифрование диска (или шифрование файлов, начиная с Android 10) — это мера, направленная на защиту данных от физического доступа и других векторов атак. Поэтому для обеспечения аналогичной защиты конфиденциальные данные, хранящиеся на внешних носителях, должны дополнительно шифроваться приложением.
Проведите проверки целостности.
В случаях, когда данные или код необходимо загрузить из внешнего хранилища в приложение, рекомендуется проводить проверки целостности, чтобы убедиться, что никакое другое приложение не внесло изменения в эти данные или код. Хэши файлов следует хранить безопасным способом, предпочтительно в зашифрованном виде, во внутреннем хранилище.
Котлин
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()
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();
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!");
}
}
}
Ресурсы
- Ограниченное хранение
- ЧТЕНИЕ ВНЕШНЕГО ХРАНИЛИЩА
- WRITE_EXTERNAL_STORAGE
- requestLegacyExternalStorage
- Обзор хранения данных и файлов
- Хранение данных (специфичное для приложения)
- Криптография
- Магазин ключей
- Шифрование на основе файлов
- Полное шифрование диска