קטגוריית 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. באמצעות נפח אחסון ייעודי לאפליקציות, אפליקציות יכולות לגשת רק לקבצים שהן יצרו בעצמן בהתקן אחסון חיצוני או לסוגי קבצים שאוחסנו באמצעות MediaStore API, כמו אודיו ווידאו. ההגנה הזו עוזרת לשמור על הפרטיות והאבטחה של המשתמשים.
Android מגרסה 11 ואילך
באפליקציות שמטרגטות ל-Android בגרסה 11 ואילך, מערכת ההפעלה מכריחה שימוש בנפח אחסון ייעודי לאפליקציות, כלומר היא מתעלמת מהדגל requestLegacyExternalStorage ומגנה אוטומטית על האחסון החיצוני של האפליקציות מפני גישה לא רצויה.
שימוש באחסון פנימי לנתונים רגישים
ללא קשר לגרסת Android שמיועדת לטירגוט, תמיד צריך לאחסן מידע אישי רגיש של אפליקציה באחסון הפנימי. הגישה לאחסון הפנימי מוגבלת אוטומטית לאפליקציה שבבעלותה האחסון, הודות לארגז החול של Android. לכן אפשר להניח שהאחסון מאובטח, אלא אם בוצע רוט במכשיר.
הצפנת מידע אישי רגיש
אם תרחישי השימוש של האפליקציה מחייבים אחסון של מידע אישי רגיש באחסון החיצוני, צריך להצפין את הנתונים. מומלץ להשתמש באלגוריתם הצפנה חזק, באמצעות Android KeyStore כדי לאחסן את המפתח בצורה בטוחה.
באופן כללי, מומלץ להצפין את כל המידע האישי הרגיש, לא משנה איפה הוא מאוחסן.
חשוב לציין שהצפנת דיסק מלאה (או הצפנה מבוססת-קובץ מ-Android 10) היא אמצעי שמטרתו להגן על נתונים מפני גישה פיזית ווקטורים אחרים של תקיפה. לכן, כדי להעניק את אותו אמצעי אבטחה, נתונים רגישים שמאוחסנים באחסון חיצוני צריכים להיות מוצפנים גם על ידי האפליקציה.
ביצוע בדיקות תקינות
במקרים שבהם צריך לטעון נתונים או קוד מהאחסון החיצוני לאפליקציה, מומלץ לבצע בדיקות תקינות כדי לוודא שאף אפליקציה אחרת לא שינתה את הנתונים או הקוד. מומלץ לאחסן את הגיבובים של הקבצים בצורה מאובטחת, רצוי מוצפנים ובאחסון הפנימי.
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()
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!");
}
}
}
משאבים
- נפח אחסון ייעודי לאפליקציות
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
- requestLegacyExternalStorage
- סקירה כללית על אחסון נתונים וקבצים
- אחסון נתונים (ספציפי לאפליקציה)
- קריפטוגרפיה
- Keystore
- הצפנה מבוססת-קבצים
- הצפנה של כל הדיסק