קטגוריית OWASP: MASVS-CODE: איכות הקוד
סקירה כללית
FileProvider, מחלקת משנה של ContentProvider, נועדה לספק שיטה מאובטחת לאפליקציה ("אפליקציית שרת") כדי לשתף קבצים עם אפליקציה אחרת ("אפליקציית לקוח"). עם זאת, אם אפליקציית הלקוח לא מטפלת כראוי בשם הקובץ שסופק על ידי אפליקציית השרת, אפליקציית שרת שנמצאת בשליטת התוקף עשויה להטמיע FileProvider משלה כדי להחליף קבצים באחסון הספציפי לאפליקציה של אפליקציית הלקוח.
השפעה
אם תוקף יכול להחליף קבצים של אפליקציה, זה עלול להוביל להרצת קוד זדוני (על ידי החלפת הקוד של האפליקציה), או לאפשר שינוי של התנהגות האפליקציה (לדוגמה, על ידי החלפת ההעדפות המשותפות של האפליקציה או קובצי הגדרה אחרים).
אמצעי צמצום סיכונים
לא לתת אמון בקלט של משתמשים
מומלץ לעבוד ללא קלט משתמשים כשמשתמשים בקריאות למערכת הקבצים, על ידי יצירת שם קובץ ייחודי כשכותבים את הקובץ שהתקבל באחסון.
במילים אחרות: כשיישום הלקוח כותב את הקובץ שהתקבל לאחסון, הוא צריך להתעלם משם הקובץ שסופק על ידי יישום השרת ולהשתמש במזהה הייחודי שנוצר באופן פנימי כשם הקובץ.
הדוגמה הזו מבוססת על הקוד שמופיע בכתובת https://developer.android.com/training/secure-file-sharing/request-file:
Kotlin
// Code in
// https://developer.android.com/training/secure-file-sharing/request-file#OpenFile
// used to obtain file descriptor (fd)
try {
val inputStream = FileInputStream(fd)
val tempFile = File.createTempFile("temp", null, cacheDir)
val outputStream = FileOutputStream(tempFile)
val buf = ByteArray(1024)
var len: Int
len = inputStream.read(buf)
while (len > 0) {
if (len != -1) {
outputStream.write(buf, 0, len)
len = inputStream.read(buf)
}
}
inputStream.close()
outputStream.close()
} catch (e: IOException) {
e.printStackTrace()
Log.e("MainActivity", "File copy error.")
return
}
Java
// Code in
// https://developer.android.com/training/secure-file-sharing/request-file#OpenFile
// used to obtain file descriptor (fd)
FileInputStream inputStream = new FileInputStream(fd);
// Create a temporary file
File tempFile = File.createTempFile("temp", null, getCacheDir());
// Copy the contents of the file to the temporary file
try {
OutputStream outputStream = new FileOutputStream(tempFile))
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
} catch (IOException e) {
e.printStackTrace();
Log.e("MainActivity", "File copy error.");
return;
}
ניקוי שמות הקבצים שסופקו
לנקות את שם הקובץ שסופק כשכותבים את הקובץ שהתקבל לאחסון.
הפתרון הזה פחות מומלץ מהפתרון הקודם, כי יכול להיות שיהיה קשה לטפל בכל המקרים האפשריים. עם זאת: אם לא ניתן ליצור שם קובץ ייחודי, אפליקציית הלקוח צריכה לבצע סניטציה לשם הקובץ שסופק. הסניטציה כוללת:
- ניקוי תווים למעבר נתיב בשם הקובץ
- ביצוע קנוניזציה כדי לוודא שאין מעברים בנתיב
הקוד לדוגמה הזה מבוסס על ההנחיות בנושא אחזור פרטי קובץ:
Kotlin
protected fun sanitizeFilename(displayName: String): String {
val badCharacters = arrayOf("..", "/")
val segments = displayName.split("/")
var fileName = segments[segments.size - 1]
for (suspString in badCharacters) {
fileName = fileName.replace(suspString, "_")
}
return fileName
}
val displayName = returnCursor.getString(nameIndex)
val fileName = sanitizeFilename(displayName)
val filePath = File(context.filesDir, fileName).path
// saferOpenFile defined in Android developer documentation
val outputFile = saferOpenFile(filePath, context.filesDir.canonicalPath)
// fd obtained using Requesting a shared file from Android developer
// documentation
val inputStream = FileInputStream(fd)
// Copy the contents of the file to the new file
try {
val outputStream = FileOutputStream(outputFile)
val buffer = ByteArray(1024)
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
}
} catch (e: IOException) {
// Handle exception
}
Java
protected String sanitizeFilename(String displayName) {
String[] badCharacters = new String[] { "..", "/" };
String[] segments = displayName.split("/");
String fileName = segments[segments.length - 1];
for (String suspString : badCharacters) {
fileName = fileName.replace(suspString, "_");
}
return fileName;
}
String displayName = returnCursor.getString(nameIndex);
String fileName = sanitizeFilename(displayName);
String filePath = new File(context.getFilesDir(), fileName).getPath();
// saferOpenFile defined in Android developer documentation
File outputFile = saferOpenFile(filePath,
context.getFilesDir().getCanonicalPath());
// fd obtained using Requesting a shared file from Android developer
// documentation
FileInputStream inputStream = new FileInputStream(fd);
// Copy the contents of the file to the new file
try {
OutputStream outputStream = new FileOutputStream(outputFile))
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
} catch (IOException e) {
// Handle exception
}
תורמים: Dimitrios Valsamaras ו-Michael Peck מ-Microsoft Threat Intelligence
משאבים
- Dirty Stream Attack: Turning Android Share Targets Into Attack Vectors
- שיתוף קבצים מאובטח
- בקשת תיעוד של קובץ משותף
- אחזור מידע
- FileProvider
- פרצת אבטחה מסוג Path Traversal
- CWE-73 External Control of Filename or Path